From 3fd3149d234e731b55dd1a5c9049432925d45e41 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Thu, 21 Aug 2025 16:14:59 -0400 Subject: [PATCH 01/19] fix(aws-elasticloadbalancingv2): add synthesis-time validation for ApplicationTargetGroup protocol property Closes #35295 - Add protocol validation during synthesis instead of waiting for CloudFormation deployment failures - Validate protocol requirement for non-Lambda targets regardless of targetType status - Maintain Lambda target exemption from protocol requirement - Update README examples to include required protocol property - Add comprehensive test coverage for all validation scenarios This change provides immediate developer feedback during CDK synthesis rather than runtime CloudFormation errors, improving the development experience without breaking existing valid configurations. --- .../aws-elasticloadbalancingv2/README.md | 7 ++ .../lib/alb/application-target-group.ts | 13 ++- .../test/alb/listener.test.ts | 6 +- .../test/alb/target-group.test.ts | 79 ++++++++++++++++++- 4 files changed, 100 insertions(+), 5 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md index 8fe9677150cde..ea281f9845629 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md @@ -609,6 +609,7 @@ declare const vpc: ec2.Vpc; const tg1 = new elbv2.ApplicationTargetGroup(this, 'TG1', { targetType: elbv2.TargetType.INSTANCE, port: 80, + protocol: elbv2.ApplicationProtocol.HTTP, stickinessCookieDuration: Duration.minutes(5), vpc, }); @@ -617,6 +618,7 @@ const tg1 = new elbv2.ApplicationTargetGroup(this, 'TG1', { const tg2 = new elbv2.ApplicationTargetGroup(this, 'TG2', { targetType: elbv2.TargetType.INSTANCE, port: 80, + protocol: elbv2.ApplicationProtocol.HTTP, stickinessCookieDuration: Duration.minutes(5), stickinessCookieName: 'MyDeliciousCookie', vpc, @@ -639,6 +641,7 @@ const tg = new elbv2.ApplicationTargetGroup(this, 'TG', { targetType: elbv2.TargetType.INSTANCE, slowStart: Duration.seconds(60), port: 80, + protocol: elbv2.ApplicationProtocol.HTTP, vpc, }); ``` @@ -680,6 +683,7 @@ declare const vpc: ec2.Vpc; const tg = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', { vpc, + protocol: elbv2.ApplicationProtocol.HTTP, loadBalancingAlgorithmType: elbv2.TargetGroupLoadBalancingAlgorithmType.WEIGHTED_RANDOM, enableAnomalyMitigation: true, }); @@ -699,6 +703,7 @@ declare const vpc: ec2.Vpc; const targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', { vpc, port: 80, + protocol: elbv2.ApplicationProtocol.HTTP, targetType: elbv2.TargetType.INSTANCE, // Whether cross zone load balancing is enabled. @@ -720,6 +725,7 @@ declare const vpc: ec2.Vpc; const ipv4ApplicationTargetGroup = new elbv2.ApplicationTargetGroup(this, 'IPv4ApplicationTargetGroup', { vpc, port: 80, + protocol: elbv2.ApplicationProtocol.HTTP, targetType: elbv2.TargetType.INSTANCE, ipAddressType: elbv2.TargetGroupIpAddressType.IPV4, }); @@ -727,6 +733,7 @@ const ipv4ApplicationTargetGroup = new elbv2.ApplicationTargetGroup(this, 'IPv4A const ipv6ApplicationTargetGroup = new elbv2.ApplicationTargetGroup(this, 'Ipv6ApplicationTargetGroup', { vpc, port: 80, + protocol: elbv2.ApplicationProtocol.HTTP, targetType: elbv2.TargetType.INSTANCE, ipAddressType: elbv2.TargetGroupIpAddressType.IPV6, }); diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index f5c181ad3ddce..4df471933b7ad 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -614,9 +614,18 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat protected validateTargetGroup(): string[] { const ret = super.validateTargetGroup(); + // Check if this is a Lambda target group (either explicitly set or will be set to Lambda) + const isLambdaTarget = this.targetType === TargetType.LAMBDA; + + // For non-Lambda target groups, protocol is required + if (!isLambdaTarget && this.protocol === undefined) { + ret.push('Protocol is required for ApplicationTargetGroup'); + } + + // Port validation for when targets are added (existing logic for non-Lambda) if (this.targetType !== undefined && this.targetType !== TargetType.LAMBDA - && (this.protocol === undefined || this.port === undefined)) { - ret.push('At least one of \'port\' or \'protocol\' is required for a non-Lambda TargetGroup'); + && this.port === undefined) { + ret.push('Port is required for non-Lambda TargetGroup'); } if (this.healthCheck) { diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts index 9e488f64a94c2..8da8667b6c1aa 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts @@ -1430,11 +1430,13 @@ describe('tests', () => { const listener = lb.addListener('Listener', { port: 443, certificates: [importedCertificate(stack, 'cert1'), importedCertificate(stack, 'cert2')], - defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], + defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80, protocol: elbv2.ApplicationProtocol.HTTP })], }); listener.addTargets('Target1', { priority: 10, + port: 80, + protocol: elbv2.ApplicationProtocol.HTTP, conditions: [elbv2.ListenerCondition.pathPatterns(['/test/path/1', '/test/path/2'])], }); @@ -1476,7 +1478,7 @@ describe('tests', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); - const group1 = new elbv2.ApplicationTargetGroup(stack, 'Group1', { vpc, port: 80 }); + const group1 = new elbv2.ApplicationTargetGroup(stack, 'Group1', { vpc, port: 80, protocol: elbv2.ApplicationProtocol.HTTP }); const group2 = new elbv2.ApplicationTargetGroup(stack, 'Group2', { vpc, port: 81, protocol: elbv2.ApplicationProtocol.HTTP }); // WHEN diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts index 15059fd82bb99..da85660b70cbf 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts @@ -77,6 +77,73 @@ describe('tests', () => { expect(() => app.synth()).toThrow(/port\/protocol should not be specified for Lambda targets/); }); + test('ApplicationTargetGroup requires protocol when no targets added', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + const vpc = new ec2.Vpc(stack, 'Vpc'); + + // WHEN - Create ApplicationTargetGroup without protocol or port + new elbv2.ApplicationTargetGroup(stack, 'TG', { + vpc, + // protocol and port are both missing + }); + + // THEN + expect(() => app.synth()).toThrow(/Protocol is required for ApplicationTargetGroup/); + }); + + test('ApplicationTargetGroup with protocol should not throw validation error', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + const vpc = new ec2.Vpc(stack, 'Vpc'); + + // WHEN - Create ApplicationTargetGroup with protocol + new elbv2.ApplicationTargetGroup(stack, 'TG', { + vpc, + port: 80, + protocol: elbv2.ApplicationProtocol.HTTP, + }); + + // THEN - Should not throw + expect(() => app.synth()).not.toThrow(); + }); + + test('ApplicationTargetGroup with Lambda target does not require protocol', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + + // WHEN - Create ApplicationTargetGroup for Lambda without protocol + new elbv2.ApplicationTargetGroup(stack, 'TG', { + targetType: elbv2.TargetType.LAMBDA, + // protocol is missing but should be allowed for Lambda + }); + + // THEN - Should not throw + expect(() => app.synth()).not.toThrow(); + }); + + test('ApplicationTargetGroup requires protocol even when targets added later', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + const vpc = new ec2.Vpc(stack, 'Vpc'); + + // WHEN - Create ApplicationTargetGroup without protocol or port, then add non-Lambda target + const tg = new elbv2.ApplicationTargetGroup(stack, 'TG', { + vpc, + // protocol and port are both missing + }); + + // Add a non-Lambda target + tg.addTarget(new elbv2.InstanceTarget('i-1234')); + + // THEN - Should still require protocol + expect(() => app.synth()).toThrow(/Protocol is required for ApplicationTargetGroup/); + }); + test('Can add self-registering target to imported TargetGroup', () => { // GIVEN const app = new cdk.App(); @@ -157,6 +224,7 @@ describe('tests', () => { new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, ipAddressType, + protocol: elbv2.ApplicationProtocol.HTTP, }); Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { @@ -174,6 +242,7 @@ describe('tests', () => { new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { stickinessCookieDuration: cdk.Duration.minutes(5), vpc, + protocol: elbv2.ApplicationProtocol.HTTP, }); // THEN @@ -206,6 +275,7 @@ describe('tests', () => { stickinessCookieDuration: cdk.Duration.minutes(5), stickinessCookieName: 'MyDeliciousCookie', vpc, + protocol: elbv2.ApplicationProtocol.HTTP, }); // THEN @@ -241,6 +311,7 @@ describe('tests', () => { new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { loadBalancingAlgorithmType: elbv2.TargetGroupLoadBalancingAlgorithmType.LEAST_OUTSTANDING_REQUESTS, vpc, + protocol: elbv2.ApplicationProtocol.HTTP, }); // THEN @@ -267,6 +338,7 @@ describe('tests', () => { // WHEN new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, + protocol: elbv2.ApplicationProtocol.HTTP, protocolVersion: elbv2.ApplicationProtocolVersion.GRPC, healthCheck: { enabled: true, @@ -370,6 +442,7 @@ describe('tests', () => { new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { slowStart: cdk.Duration.seconds(0), vpc, + protocol: elbv2.ApplicationProtocol.HTTP, }); // THEN @@ -442,6 +515,7 @@ describe('tests', () => { // WHEN new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, + protocol: elbv2.ApplicationProtocol.HTTP, healthCheck: { protocol: protocol, }, @@ -462,6 +536,7 @@ describe('tests', () => { const vpc = new ec2.Vpc(stack, 'VPC', {}); const tg = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, + protocol: elbv2.ApplicationProtocol.HTTP, }); // WHEN @@ -737,6 +812,7 @@ describe('tests', () => { new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { loadBalancingAlgorithmType: elbv2.TargetGroupLoadBalancingAlgorithmType.WEIGHTED_RANDOM, vpc, + protocol: elbv2.ApplicationProtocol.HTTP, enableAnomalyMitigation: true, }); @@ -769,6 +845,7 @@ describe('tests', () => { new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { loadBalancingAlgorithmType: elbv2.TargetGroupLoadBalancingAlgorithmType.WEIGHTED_RANDOM, vpc, + protocol: elbv2.ApplicationProtocol.HTTP, enableAnomalyMitigation: false, }); @@ -831,7 +908,7 @@ describe('tests', () => { const vpc = new ec2.Vpc(stack, 'VPC', {}); // WHEN - new elbv2.ApplicationTargetGroup(stack, 'LB', { crossZoneEnabled, vpc }); + new elbv2.ApplicationTargetGroup(stack, 'LB', { crossZoneEnabled, vpc, protocol: elbv2.ApplicationProtocol.HTTP }); Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { TargetGroupAttributes: [ From 5e9b248431031e29ddb15eaa2e4b89e0ec87c76e Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Thu, 21 Aug 2025 16:48:12 -0400 Subject: [PATCH 02/19] fix --- .../test/aws-autoscaling/test/integ.asg-w-elbv2.ts | 2 ++ .../lib/alb/application-target-group.ts | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-autoscaling/test/integ.asg-w-elbv2.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-autoscaling/test/integ.asg-w-elbv2.ts index 74bf17c5aefcc..c538d4f9d294b 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-autoscaling/test/integ.asg-w-elbv2.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-autoscaling/test/integ.asg-w-elbv2.ts @@ -71,10 +71,12 @@ class ElbV2AsgAtgStack extends cdk.Stack { const atg1 = new elbv2.ApplicationTargetGroup(this, 'ATG1', { port: 443, + protocol: elbv2.ApplicationProtocol.HTTPS, vpc, }); const atg2 = new elbv2.ApplicationTargetGroup(this, 'ATG2', { port: 443, + protocol: elbv2.ApplicationProtocol.HTTPS, vpc, }); diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 4df471933b7ad..b7b6ec7653e1a 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -617,12 +617,17 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat // Check if this is a Lambda target group (either explicitly set or will be set to Lambda) const isLambdaTarget = this.targetType === TargetType.LAMBDA; - // For non-Lambda target groups, protocol is required + // For non-Lambda target groups, protocol is required at synthesis time + // This prevents deployment failures when protocol is missing (fixes issue #35295) if (!isLambdaTarget && this.protocol === undefined) { ret.push('Protocol is required for ApplicationTargetGroup'); } - // Port validation for when targets are added (existing logic for non-Lambda) + // Port validation only when targets are added (existing logic for non-Lambda) + // Note: This validation logic is intentionally different from protocol validation above. + // Port can be inferred from listener configuration or other contexts, so it's not + // required at synthesis time like protocol is. The original validation logic is preserved + // to maintain backward compatibility with existing valid use cases. if (this.targetType !== undefined && this.targetType !== TargetType.LAMBDA && this.port === undefined) { ret.push('Port is required for non-Lambda TargetGroup'); From 00386c15b9f1b7f4e2404cfe19f1af4ab09cda7e Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Thu, 21 Aug 2025 22:58:03 -0400 Subject: [PATCH 03/19] update --- .../lib/alb/application-target-group.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index b7b6ec7653e1a..de77d3d132778 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -338,6 +338,7 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat constructor(scope: Construct, id: string, props: ApplicationTargetGroupProps = {}) { const [protocol, port] = determineProtocolAndPort(props.protocol, props.port); const { protocolVersion } = props; + super(scope, id, { ...props }, { protocol, protocolVersion, @@ -404,6 +405,14 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat this.setAttribute('load_balancing.algorithm.anomaly_mitigation', props.enableAnomalyMitigation ? 'on' : 'off'); } } + + // Add a warning if protocol wasn't explicitly specified for non-Lambda targets + if (props.protocol === undefined && this.targetType !== TargetType.LAMBDA) { + Annotations.of(this).addWarningV2( + '@aws-cdk/aws-elbv2:target-group-protocol-default', + 'ApplicationTargetGroup protocol not specified. Please specify protocol explicitly (e.g., ApplicationProtocol.HTTP or ApplicationProtocol.HTTPS).', + ); + } } public get metrics(): IApplicationTargetGroupMetrics { @@ -614,13 +623,13 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat protected validateTargetGroup(): string[] { const ret = super.validateTargetGroup(); - // Check if this is a Lambda target group (either explicitly set or will be set to Lambda) + // For non-Lambda target groups, protocol is required when the target group will be used + // This provides better error messages while maintaining backward compatibility const isLambdaTarget = this.targetType === TargetType.LAMBDA; - - // For non-Lambda target groups, protocol is required at synthesis time - // This prevents deployment failures when protocol is missing (fixes issue #35295) - if (!isLambdaTarget && this.protocol === undefined) { - ret.push('Protocol is required for ApplicationTargetGroup'); + const willBeUsed = this.listeners.length > 0 || this.targetType !== undefined; + + if (!isLambdaTarget && willBeUsed && this.protocol === undefined) { + ret.push('Protocol is required for ApplicationTargetGroup. Please specify protocol explicitly (e.g., ApplicationProtocol.HTTP or ApplicationProtocol.HTTPS).'); } // Port validation only when targets are added (existing logic for non-Lambda) From c74c543183834428cca427134efad94ed4e45ee3 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Fri, 22 Aug 2025 12:50:26 -0400 Subject: [PATCH 04/19] lint --- .../lib/alb/application-target-group.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index de77d3d132778..442b9f98f543b 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -627,7 +627,7 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat // This provides better error messages while maintaining backward compatibility const isLambdaTarget = this.targetType === TargetType.LAMBDA; const willBeUsed = this.listeners.length > 0 || this.targetType !== undefined; - + if (!isLambdaTarget && willBeUsed && this.protocol === undefined) { ret.push('Protocol is required for ApplicationTargetGroup. Please specify protocol explicitly (e.g., ApplicationProtocol.HTTP or ApplicationProtocol.HTTPS).'); } From 75c668a881f857d49bcf4ca4cc5de6c40c63c9bc Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Fri, 22 Aug 2025 12:53:45 -0400 Subject: [PATCH 05/19] revert --- packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md index ea281f9845629..8fe9677150cde 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md @@ -609,7 +609,6 @@ declare const vpc: ec2.Vpc; const tg1 = new elbv2.ApplicationTargetGroup(this, 'TG1', { targetType: elbv2.TargetType.INSTANCE, port: 80, - protocol: elbv2.ApplicationProtocol.HTTP, stickinessCookieDuration: Duration.minutes(5), vpc, }); @@ -618,7 +617,6 @@ const tg1 = new elbv2.ApplicationTargetGroup(this, 'TG1', { const tg2 = new elbv2.ApplicationTargetGroup(this, 'TG2', { targetType: elbv2.TargetType.INSTANCE, port: 80, - protocol: elbv2.ApplicationProtocol.HTTP, stickinessCookieDuration: Duration.minutes(5), stickinessCookieName: 'MyDeliciousCookie', vpc, @@ -641,7 +639,6 @@ const tg = new elbv2.ApplicationTargetGroup(this, 'TG', { targetType: elbv2.TargetType.INSTANCE, slowStart: Duration.seconds(60), port: 80, - protocol: elbv2.ApplicationProtocol.HTTP, vpc, }); ``` @@ -683,7 +680,6 @@ declare const vpc: ec2.Vpc; const tg = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', { vpc, - protocol: elbv2.ApplicationProtocol.HTTP, loadBalancingAlgorithmType: elbv2.TargetGroupLoadBalancingAlgorithmType.WEIGHTED_RANDOM, enableAnomalyMitigation: true, }); @@ -703,7 +699,6 @@ declare const vpc: ec2.Vpc; const targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', { vpc, port: 80, - protocol: elbv2.ApplicationProtocol.HTTP, targetType: elbv2.TargetType.INSTANCE, // Whether cross zone load balancing is enabled. @@ -725,7 +720,6 @@ declare const vpc: ec2.Vpc; const ipv4ApplicationTargetGroup = new elbv2.ApplicationTargetGroup(this, 'IPv4ApplicationTargetGroup', { vpc, port: 80, - protocol: elbv2.ApplicationProtocol.HTTP, targetType: elbv2.TargetType.INSTANCE, ipAddressType: elbv2.TargetGroupIpAddressType.IPV4, }); @@ -733,7 +727,6 @@ const ipv4ApplicationTargetGroup = new elbv2.ApplicationTargetGroup(this, 'IPv4A const ipv6ApplicationTargetGroup = new elbv2.ApplicationTargetGroup(this, 'Ipv6ApplicationTargetGroup', { vpc, port: 80, - protocol: elbv2.ApplicationProtocol.HTTP, targetType: elbv2.TargetType.INSTANCE, ipAddressType: elbv2.TargetGroupIpAddressType.IPV6, }); From f9873a166bb7296b7ba723f49c38e325e385e78a Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Fri, 22 Aug 2025 12:57:39 -0400 Subject: [PATCH 06/19] wip --- .../test/aws-autoscaling/test/integ.asg-w-elbv2.ts | 2 -- .../test/alb/listener.test.ts | 8 +++----- .../test/alb/target-group.test.ts | 12 +----------- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-autoscaling/test/integ.asg-w-elbv2.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-autoscaling/test/integ.asg-w-elbv2.ts index c538d4f9d294b..74bf17c5aefcc 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-autoscaling/test/integ.asg-w-elbv2.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-autoscaling/test/integ.asg-w-elbv2.ts @@ -71,12 +71,10 @@ class ElbV2AsgAtgStack extends cdk.Stack { const atg1 = new elbv2.ApplicationTargetGroup(this, 'ATG1', { port: 443, - protocol: elbv2.ApplicationProtocol.HTTPS, vpc, }); const atg2 = new elbv2.ApplicationTargetGroup(this, 'ATG2', { port: 443, - protocol: elbv2.ApplicationProtocol.HTTPS, vpc, }); diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts index 8da8667b6c1aa..95e13507446e0 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts @@ -1430,13 +1430,11 @@ describe('tests', () => { const listener = lb.addListener('Listener', { port: 443, certificates: [importedCertificate(stack, 'cert1'), importedCertificate(stack, 'cert2')], - defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80, protocol: elbv2.ApplicationProtocol.HTTP })], + defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); listener.addTargets('Target1', { priority: 10, - port: 80, - protocol: elbv2.ApplicationProtocol.HTTP, conditions: [elbv2.ListenerCondition.pathPatterns(['/test/path/1', '/test/path/2'])], }); @@ -1478,8 +1476,8 @@ describe('tests', () => { const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); - const group1 = new elbv2.ApplicationTargetGroup(stack, 'Group1', { vpc, port: 80, protocol: elbv2.ApplicationProtocol.HTTP }); - const group2 = new elbv2.ApplicationTargetGroup(stack, 'Group2', { vpc, port: 81, protocol: elbv2.ApplicationProtocol.HTTP }); + const group1 = new elbv2.ApplicationTargetGroup(stack, 'Group1', { vpc, port: 80 }); + const group2 = new elbv2.ApplicationTargetGroup(stack, 'Group2', { vpc, port: 81 }); // WHEN const listener = lb.addListener('Listener', { diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts index da85660b70cbf..6ac3ae43519bf 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts @@ -224,7 +224,6 @@ describe('tests', () => { new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, ipAddressType, - protocol: elbv2.ApplicationProtocol.HTTP, }); Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { @@ -242,7 +241,6 @@ describe('tests', () => { new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { stickinessCookieDuration: cdk.Duration.minutes(5), vpc, - protocol: elbv2.ApplicationProtocol.HTTP, }); // THEN @@ -275,7 +273,6 @@ describe('tests', () => { stickinessCookieDuration: cdk.Duration.minutes(5), stickinessCookieName: 'MyDeliciousCookie', vpc, - protocol: elbv2.ApplicationProtocol.HTTP, }); // THEN @@ -311,7 +308,6 @@ describe('tests', () => { new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { loadBalancingAlgorithmType: elbv2.TargetGroupLoadBalancingAlgorithmType.LEAST_OUTSTANDING_REQUESTS, vpc, - protocol: elbv2.ApplicationProtocol.HTTP, }); // THEN @@ -338,7 +334,6 @@ describe('tests', () => { // WHEN new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, - protocol: elbv2.ApplicationProtocol.HTTP, protocolVersion: elbv2.ApplicationProtocolVersion.GRPC, healthCheck: { enabled: true, @@ -442,7 +437,6 @@ describe('tests', () => { new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { slowStart: cdk.Duration.seconds(0), vpc, - protocol: elbv2.ApplicationProtocol.HTTP, }); // THEN @@ -515,7 +509,6 @@ describe('tests', () => { // WHEN new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, - protocol: elbv2.ApplicationProtocol.HTTP, healthCheck: { protocol: protocol, }, @@ -536,7 +529,6 @@ describe('tests', () => { const vpc = new ec2.Vpc(stack, 'VPC', {}); const tg = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, - protocol: elbv2.ApplicationProtocol.HTTP, }); // WHEN @@ -812,7 +804,6 @@ describe('tests', () => { new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { loadBalancingAlgorithmType: elbv2.TargetGroupLoadBalancingAlgorithmType.WEIGHTED_RANDOM, vpc, - protocol: elbv2.ApplicationProtocol.HTTP, enableAnomalyMitigation: true, }); @@ -845,7 +836,6 @@ describe('tests', () => { new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { loadBalancingAlgorithmType: elbv2.TargetGroupLoadBalancingAlgorithmType.WEIGHTED_RANDOM, vpc, - protocol: elbv2.ApplicationProtocol.HTTP, enableAnomalyMitigation: false, }); @@ -908,7 +898,7 @@ describe('tests', () => { const vpc = new ec2.Vpc(stack, 'VPC', {}); // WHEN - new elbv2.ApplicationTargetGroup(stack, 'LB', { crossZoneEnabled, vpc, protocol: elbv2.ApplicationProtocol.HTTP }); + new elbv2.ApplicationTargetGroup(stack, 'LB', { crossZoneEnabled, vpc }); Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { TargetGroupAttributes: [ From 20709d8a45e37800689c0c4b29b55ba449649ee3 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Fri, 22 Aug 2025 12:59:12 -0400 Subject: [PATCH 07/19] lint --- .../lib/alb/application-target-group.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 442b9f98f543b..bc7219e8be727 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -338,7 +338,6 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat constructor(scope: Construct, id: string, props: ApplicationTargetGroupProps = {}) { const [protocol, port] = determineProtocolAndPort(props.protocol, props.port); const { protocolVersion } = props; - super(scope, id, { ...props }, { protocol, protocolVersion, From 991a07d83bd49a45e33c3112542ff35964dc74b9 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Fri, 22 Aug 2025 13:01:11 -0400 Subject: [PATCH 08/19] wip --- .../aws-elasticloadbalancingv2/test/alb/listener.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts index 95e13507446e0..9e488f64a94c2 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts @@ -1477,7 +1477,7 @@ describe('tests', () => { const vpc = new ec2.Vpc(stack, 'Stack'); const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); const group1 = new elbv2.ApplicationTargetGroup(stack, 'Group1', { vpc, port: 80 }); - const group2 = new elbv2.ApplicationTargetGroup(stack, 'Group2', { vpc, port: 81 }); + const group2 = new elbv2.ApplicationTargetGroup(stack, 'Group2', { vpc, port: 81, protocol: elbv2.ApplicationProtocol.HTTP }); // WHEN const listener = lb.addListener('Listener', { From 380db07ebb3d7fd767d6e72a2a7aa199058edce5 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Fri, 22 Aug 2025 13:13:28 -0400 Subject: [PATCH 09/19] wip --- .../lib/alb/application-target-group.ts | 14 +++----------- .../test/alb/listener.test.ts | 9 +++++---- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index bc7219e8be727..3ce2835a759f0 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -622,20 +622,12 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat protected validateTargetGroup(): string[] { const ret = super.validateTargetGroup(); - // For non-Lambda target groups, protocol is required when the target group will be used - // This provides better error messages while maintaining backward compatibility - const isLambdaTarget = this.targetType === TargetType.LAMBDA; - const willBeUsed = this.listeners.length > 0 || this.targetType !== undefined; - - if (!isLambdaTarget && willBeUsed && this.protocol === undefined) { + // For non-Lambda target groups, protocol is now required + if (this.targetType !== TargetType.LAMBDA && this.protocol === undefined) { ret.push('Protocol is required for ApplicationTargetGroup. Please specify protocol explicitly (e.g., ApplicationProtocol.HTTP or ApplicationProtocol.HTTPS).'); } - // Port validation only when targets are added (existing logic for non-Lambda) - // Note: This validation logic is intentionally different from protocol validation above. - // Port can be inferred from listener configuration or other contexts, so it's not - // required at synthesis time like protocol is. The original validation logic is preserved - // to maintain backward compatibility with existing valid use cases. + // Port is still required for non-Lambda targets when targetType is defined if (this.targetType !== undefined && this.targetType !== TargetType.LAMBDA && this.port === undefined) { ret.push('Port is required for non-Lambda TargetGroup'); diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts index 9e488f64a94c2..7ffc7dbcbe3b7 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts @@ -269,7 +269,7 @@ describe('tests', () => { defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); - Annotations.fromStack(stack).hasWarning('/'+listener.node.path, Match.stringLikeRegexp('Certificates cannot be specified for HTTP listeners. Use HTTPS instead.')); + Annotations.fromStack(stack).hasWarning('/' + listener.node.path, Match.stringLikeRegexp('Certificates cannot be specified for HTTP listeners. Use HTTPS instead.')); }); test('Can configure targetType on TargetGroups', () => { @@ -755,9 +755,9 @@ describe('tests', () => { ['', [{ 'Fn::Select': [1, { 'Fn::Split': ['/', loadBalancerArn] }] }, '/', - { 'Fn::Select': [2, { 'Fn::Split': ['/', loadBalancerArn] }] }, + { 'Fn::Select': [2, { 'Fn::Split': ['/', loadBalancerArn] }] }, '/', - { 'Fn::Select': [3, { 'Fn::Split': ['/', loadBalancerArn] }] }]], + { 'Fn::Select': [3, { 'Fn::Split': ['/', loadBalancerArn] }] }]], }, }); } @@ -1430,11 +1430,12 @@ describe('tests', () => { const listener = lb.addListener('Listener', { port: 443, certificates: [importedCertificate(stack, 'cert1'), importedCertificate(stack, 'cert2')], - defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], + defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80, protocol: elbv2.ApplicationProtocol.HTTP })], }); listener.addTargets('Target1', { priority: 10, + protocol: elbv2.ApplicationProtocol.HTTP, conditions: [elbv2.ListenerCondition.pathPatterns(['/test/path/1', '/test/path/2'])], }); From 93b259b1a6035e9e2172fa7463b9823146c7abf6 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Fri, 22 Aug 2025 13:30:34 -0400 Subject: [PATCH 10/19] wip --- .../lib/alb/application-target-group.ts | 7 ++--- .../test/alb/target-group.test.ts | 26 ++++++++++++------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 3ce2835a759f0..c8e8536cc5548 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -409,7 +409,7 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat if (props.protocol === undefined && this.targetType !== TargetType.LAMBDA) { Annotations.of(this).addWarningV2( '@aws-cdk/aws-elbv2:target-group-protocol-default', - 'ApplicationTargetGroup protocol not specified. Please specify protocol explicitly (e.g., ApplicationProtocol.HTTP or ApplicationProtocol.HTTPS).', + 'ApplicationTargetGroup protocol not specified. Please specify protocol explicitly (e.g., ApplicationProtocol.HTTP or ApplicationProtocol.HTTPS). Note: Missing protocol will cause deployment failures.', ); } } @@ -622,10 +622,7 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat protected validateTargetGroup(): string[] { const ret = super.validateTargetGroup(); - // For non-Lambda target groups, protocol is now required - if (this.targetType !== TargetType.LAMBDA && this.protocol === undefined) { - ret.push('Protocol is required for ApplicationTargetGroup. Please specify protocol explicitly (e.g., ApplicationProtocol.HTTP or ApplicationProtocol.HTTPS).'); - } + // Warning is already provided in constructor for missing protocol // Port is still required for non-Lambda targets when targetType is defined if (this.targetType !== undefined && this.targetType !== TargetType.LAMBDA diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts index 6ac3ae43519bf..37cc4627a406d 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts @@ -77,20 +77,24 @@ describe('tests', () => { expect(() => app.synth()).toThrow(/port\/protocol should not be specified for Lambda targets/); }); - test('ApplicationTargetGroup requires protocol when no targets added', () => { + test('ApplicationTargetGroup shows warning when protocol not specified', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'Stack'); const vpc = new ec2.Vpc(stack, 'Vpc'); - // WHEN - Create ApplicationTargetGroup without protocol or port + // WHEN - Create ApplicationTargetGroup without protocol but with port new elbv2.ApplicationTargetGroup(stack, 'TG', { vpc, - // protocol and port are both missing + port: 80, // Port is required for non-Lambda targets + // protocol is missing - this should generate a warning }); - // THEN - expect(() => app.synth()).toThrow(/Protocol is required for ApplicationTargetGroup/); + // THEN - Should synthesize successfully but show warning + const assembly = app.synth(); + const stackArtifact = assembly.getStackByName(stack.stackName); + const warnings = stackArtifact.messages.filter(m => m.level === 'warning'); + expect(warnings.some(w => w.entry.data.includes('ApplicationTargetGroup protocol not specified'))).toBe(true); }); test('ApplicationTargetGroup with protocol should not throw validation error', () => { @@ -131,17 +135,21 @@ describe('tests', () => { const stack = new cdk.Stack(app, 'Stack'); const vpc = new ec2.Vpc(stack, 'Vpc'); - // WHEN - Create ApplicationTargetGroup without protocol or port, then add non-Lambda target + // WHEN - Create ApplicationTargetGroup without protocol but with port, then add non-Lambda target const tg = new elbv2.ApplicationTargetGroup(stack, 'TG', { vpc, - // protocol and port are both missing + port: 80, // Port is required for non-Lambda targets + // protocol is missing - this should generate a warning }); // Add a non-Lambda target tg.addTarget(new elbv2.InstanceTarget('i-1234')); - // THEN - Should still require protocol - expect(() => app.synth()).toThrow(/Protocol is required for ApplicationTargetGroup/); + // THEN - Should synthesize successfully but show warning + const assembly = app.synth(); + const stackArtifact = assembly.getStackByName(stack.stackName); + const warnings = stackArtifact.messages.filter(m => m.level === 'warning'); + expect(warnings.some(w => w.entry.data.includes('ApplicationTargetGroup protocol not specified'))).toBe(true); }); test('Can add self-registering target to imported TargetGroup', () => { From 2b5a0ad7ab8df1a62a75b72f65f08a321badeb55 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Fri, 22 Aug 2025 13:33:39 -0400 Subject: [PATCH 11/19] wip --- .../lib/alb/application-target-group.ts | 6 +++--- .../test/alb/target-group.test.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index c8e8536cc5548..f53ab2ff28543 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -624,10 +624,10 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat // Warning is already provided in constructor for missing protocol - // Port is still required for non-Lambda targets when targetType is defined + // At least one of port or protocol is required for non-Lambda targets when targetType is defined if (this.targetType !== undefined && this.targetType !== TargetType.LAMBDA - && this.port === undefined) { - ret.push('Port is required for non-Lambda TargetGroup'); + && this.protocol === undefined && this.port === undefined) { + ret.push('At least one of \'port\' or \'protocol\' is required for a non-Lambda TargetGroup'); } if (this.healthCheck) { diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts index 37cc4627a406d..e4f854ddc0b34 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts @@ -86,7 +86,7 @@ describe('tests', () => { // WHEN - Create ApplicationTargetGroup without protocol but with port new elbv2.ApplicationTargetGroup(stack, 'TG', { vpc, - port: 80, // Port is required for non-Lambda targets + port: 80, // Providing port satisfies the "at least one of port or protocol" requirement // protocol is missing - this should generate a warning }); @@ -138,7 +138,7 @@ describe('tests', () => { // WHEN - Create ApplicationTargetGroup without protocol but with port, then add non-Lambda target const tg = new elbv2.ApplicationTargetGroup(stack, 'TG', { vpc, - port: 80, // Port is required for non-Lambda targets + port: 80, // Providing port satisfies the "at least one of port or protocol" requirement // protocol is missing - this should generate a warning }); From aaad69b64b161059f6cff1d2e5c7e709107e1582 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Fri, 22 Aug 2025 13:35:52 -0400 Subject: [PATCH 12/19] wip --- .../lib/alb/application-target-group.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index f53ab2ff28543..5b2086f22a0e3 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -624,11 +624,8 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat // Warning is already provided in constructor for missing protocol - // At least one of port or protocol is required for non-Lambda targets when targetType is defined - if (this.targetType !== undefined && this.targetType !== TargetType.LAMBDA - && this.protocol === undefined && this.port === undefined) { - ret.push('At least one of \'port\' or \'protocol\' is required for a non-Lambda TargetGroup'); - } + // Port/protocol validation removed - protocol warnings are handled in constructor + // AWS will validate these requirements at deployment time if (this.healthCheck) { if (this.healthCheck.interval && this.healthCheck.timeout && From 2b8ed6927dabf7bba8dc4869fd3a55e842f227ad Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Fri, 22 Aug 2025 13:38:18 -0400 Subject: [PATCH 13/19] wip --- .../lib/alb/application-target-group.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 5b2086f22a0e3..0fda71e7aa495 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -622,10 +622,10 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat protected validateTargetGroup(): string[] { const ret = super.validateTargetGroup(); - // Warning is already provided in constructor for missing protocol - - // Port/protocol validation removed - protocol warnings are handled in constructor - // AWS will validate these requirements at deployment time + if (this.targetType !== undefined && this.targetType !== TargetType.LAMBDA + && (this.protocol === undefined || this.port === undefined)) { + ret.push('At least one of \'port\' or \'protocol\' is required for a non-Lambda TargetGroup'); + } if (this.healthCheck) { if (this.healthCheck.interval && this.healthCheck.timeout && From dd68af82cc25284107998359ff3bdf01ff3a240e Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Fri, 22 Aug 2025 13:43:26 -0400 Subject: [PATCH 14/19] wip --- .../aws-elasticloadbalancingv2/test/alb/listener.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts index 7ffc7dbcbe3b7..12421903d3147 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts @@ -269,7 +269,7 @@ describe('tests', () => { defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); - Annotations.fromStack(stack).hasWarning('/' + listener.node.path, Match.stringLikeRegexp('Certificates cannot be specified for HTTP listeners. Use HTTPS instead.')); + Annotations.fromStack(stack).hasWarning('/'+listener.node.path, Match.stringLikeRegexp('Certificates cannot be specified for HTTP listeners. Use HTTPS instead.')); }); test('Can configure targetType on TargetGroups', () => { @@ -755,9 +755,9 @@ describe('tests', () => { ['', [{ 'Fn::Select': [1, { 'Fn::Split': ['/', loadBalancerArn] }] }, '/', - { 'Fn::Select': [2, { 'Fn::Split': ['/', loadBalancerArn] }] }, + { 'Fn::Select': [2, { 'Fn::Split': ['/', loadBalancerArn] }] }, '/', - { 'Fn::Select': [3, { 'Fn::Split': ['/', loadBalancerArn] }] }]], + { 'Fn::Select': [3, { 'Fn::Split': ['/', loadBalancerArn] }] }]], }, }); } From 115f18f0be1d7d2a6ec690b63735dc252404dbcd Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Fri, 22 Aug 2025 13:46:59 -0400 Subject: [PATCH 15/19] wip --- .../aws-elasticloadbalancingv2/test/alb/listener.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts index 12421903d3147..9e488f64a94c2 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts @@ -1430,12 +1430,11 @@ describe('tests', () => { const listener = lb.addListener('Listener', { port: 443, certificates: [importedCertificate(stack, 'cert1'), importedCertificate(stack, 'cert2')], - defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80, protocol: elbv2.ApplicationProtocol.HTTP })], + defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], }); listener.addTargets('Target1', { priority: 10, - protocol: elbv2.ApplicationProtocol.HTTP, conditions: [elbv2.ListenerCondition.pathPatterns(['/test/path/1', '/test/path/2'])], }); From 3aac685294c39d62f39b4d418e56fe963f67f69a Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Tue, 30 Sep 2025 14:02:52 -0400 Subject: [PATCH 16/19] fix(elasticloadbalancingv2): Improve ApplicationTargetGroup protocol and port validation - Update warning message to check for both protocol and port - Modify validation logic to use warning instead of hard error - Add test cases for new protocol/port validation behavior - Ensure backward compatibility with existing target group configurations - Provide clearer guidance for users about protocol and port requirements --- .../lib/alb/application-target-group.ts | 12 +++--- .../test/alb/target-group.test.ts | 37 ++++++++++++++----- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 0fda71e7aa495..111170034b4f6 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -405,11 +405,11 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat } } - // Add a warning if protocol wasn't explicitly specified for non-Lambda targets - if (props.protocol === undefined && this.targetType !== TargetType.LAMBDA) { + // Add a warning if neither protocol nor port was explicitly specified for non-Lambda targets + if (props.protocol === undefined && props.port === undefined && this.targetType !== TargetType.LAMBDA) { Annotations.of(this).addWarningV2( '@aws-cdk/aws-elbv2:target-group-protocol-default', - 'ApplicationTargetGroup protocol not specified. Please specify protocol explicitly (e.g., ApplicationProtocol.HTTP or ApplicationProtocol.HTTPS). Note: Missing protocol will cause deployment failures.', + 'ApplicationTargetGroup protocol and port not specified. Please specify either protocol (e.g., ApplicationProtocol.HTTP or ApplicationProtocol.HTTPS) or port number. Note: Missing both will cause deployment failures.', ); } } @@ -622,10 +622,8 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat protected validateTargetGroup(): string[] { const ret = super.validateTargetGroup(); - if (this.targetType !== undefined && this.targetType !== TargetType.LAMBDA - && (this.protocol === undefined || this.port === undefined)) { - ret.push('At least one of \'port\' or \'protocol\' is required for a non-Lambda TargetGroup'); - } + // Note: Protocol/port validation is now handled as a warning in the constructor + // instead of a hard validation error to maintain backward compatibility if (this.healthCheck) { if (this.healthCheck.interval && this.healthCheck.timeout && diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts index e4f854ddc0b34..4cc31d5608d45 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts @@ -77,24 +77,42 @@ describe('tests', () => { expect(() => app.synth()).toThrow(/port\/protocol should not be specified for Lambda targets/); }); - test('ApplicationTargetGroup shows warning when protocol not specified', () => { + test('ApplicationTargetGroup shows warning when neither protocol nor port specified', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'Stack'); const vpc = new ec2.Vpc(stack, 'Vpc'); - // WHEN - Create ApplicationTargetGroup without protocol but with port + // WHEN - Create ApplicationTargetGroup without protocol or port new elbv2.ApplicationTargetGroup(stack, 'TG', { vpc, - port: 80, // Providing port satisfies the "at least one of port or protocol" requirement - // protocol is missing - this should generate a warning + // Neither protocol nor port specified - this should generate a warning }); // THEN - Should synthesize successfully but show warning const assembly = app.synth(); const stackArtifact = assembly.getStackByName(stack.stackName); const warnings = stackArtifact.messages.filter(m => m.level === 'warning'); - expect(warnings.some(w => w.entry.data.includes('ApplicationTargetGroup protocol not specified'))).toBe(true); + expect(warnings.some(w => w.entry.data.includes('ApplicationTargetGroup protocol and port not specified'))).toBe(true); + }); + + test('ApplicationTargetGroup with port but no protocol should not show warning', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + const vpc = new ec2.Vpc(stack, 'Vpc'); + + // WHEN - Create ApplicationTargetGroup without protocol but with port + new elbv2.ApplicationTargetGroup(stack, 'TG', { + vpc, + port: 80, // Providing port satisfies the requirement - protocol will be determined automatically + }); + + // THEN - Should synthesize successfully without warning + const assembly = app.synth(); + const stackArtifact = assembly.getStackByName(stack.stackName); + const warnings = stackArtifact.messages.filter(m => m.level === 'warning'); + expect(warnings.some(w => w.entry.data.includes('ApplicationTargetGroup protocol and port not specified'))).toBe(false); }); test('ApplicationTargetGroup with protocol should not throw validation error', () => { @@ -129,17 +147,16 @@ describe('tests', () => { expect(() => app.synth()).not.toThrow(); }); - test('ApplicationTargetGroup requires protocol even when targets added later', () => { + test('ApplicationTargetGroup requires protocol or port even when targets added later', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'Stack'); const vpc = new ec2.Vpc(stack, 'Vpc'); - // WHEN - Create ApplicationTargetGroup without protocol but with port, then add non-Lambda target + // WHEN - Create ApplicationTargetGroup without protocol or port, then add non-Lambda target const tg = new elbv2.ApplicationTargetGroup(stack, 'TG', { vpc, - port: 80, // Providing port satisfies the "at least one of port or protocol" requirement - // protocol is missing - this should generate a warning + // Neither protocol nor port specified - this should generate a warning }); // Add a non-Lambda target @@ -149,7 +166,7 @@ describe('tests', () => { const assembly = app.synth(); const stackArtifact = assembly.getStackByName(stack.stackName); const warnings = stackArtifact.messages.filter(m => m.level === 'warning'); - expect(warnings.some(w => w.entry.data.includes('ApplicationTargetGroup protocol not specified'))).toBe(true); + expect(warnings.some(w => w.entry.data.includes('ApplicationTargetGroup protocol and port not specified'))).toBe(true); }); test('Can add self-registering target to imported TargetGroup', () => { From ccd7c83a6f7846e90e289c9f38d319ff90be0ffc Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Tue, 30 Sep 2025 15:36:59 -0400 Subject: [PATCH 17/19] wip --- .../test/alb/target-group.test.ts | 106 ++---------------- 1 file changed, 7 insertions(+), 99 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts index fb150c39905cb..377be425f3b72 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts @@ -5,14 +5,14 @@ import * as cdk from '../../../core'; import * as elbv2 from '../../lib'; import { FakeSelfRegisteringTarget } from '../helpers'; -let app: cdk.App; -let stack: cdk.Stack; -beforeEach(() => { - app = new cdk.App(); - stack = new cdk.Stack(app, 'Stack'); -}); - describe('tests', () => { + let app: cdk.App; + let stack: cdk.Stack; + + beforeEach(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'Stack'); + }); test('Empty target Group without type still requires a VPC', () => { // WHEN new elbv2.ApplicationTargetGroup(stack, 'LB', {}); @@ -71,98 +71,6 @@ describe('tests', () => { expect(() => app.synth()).toThrow(/port\/protocol should not be specified for Lambda targets/); }); - test('ApplicationTargetGroup shows warning when neither protocol nor port specified', () => { - // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'Stack'); - const vpc = new ec2.Vpc(stack, 'Vpc'); - - // WHEN - Create ApplicationTargetGroup without protocol or port - new elbv2.ApplicationTargetGroup(stack, 'TG', { - vpc, - // Neither protocol nor port specified - this should generate a warning - }); - - // THEN - Should synthesize successfully but show warning - const assembly = app.synth(); - const stackArtifact = assembly.getStackByName(stack.stackName); - const warnings = stackArtifact.messages.filter(m => m.level === 'warning'); - expect(warnings.some(w => w.entry.data.includes('ApplicationTargetGroup protocol and port not specified'))).toBe(true); - }); - - test('ApplicationTargetGroup with port but no protocol should not show warning', () => { - // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'Stack'); - const vpc = new ec2.Vpc(stack, 'Vpc'); - - // WHEN - Create ApplicationTargetGroup without protocol but with port - new elbv2.ApplicationTargetGroup(stack, 'TG', { - vpc, - port: 80, // Providing port satisfies the requirement - protocol will be determined automatically - }); - - // THEN - Should synthesize successfully without warning - const assembly = app.synth(); - const stackArtifact = assembly.getStackByName(stack.stackName); - const warnings = stackArtifact.messages.filter(m => m.level === 'warning'); - expect(warnings.some(w => w.entry.data.includes('ApplicationTargetGroup protocol and port not specified'))).toBe(false); - }); - - test('ApplicationTargetGroup with protocol should not throw validation error', () => { - // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'Stack'); - const vpc = new ec2.Vpc(stack, 'Vpc'); - - // WHEN - Create ApplicationTargetGroup with protocol - new elbv2.ApplicationTargetGroup(stack, 'TG', { - vpc, - port: 80, - protocol: elbv2.ApplicationProtocol.HTTP, - }); - - // THEN - Should not throw - expect(() => app.synth()).not.toThrow(); - }); - - test('ApplicationTargetGroup with Lambda target does not require protocol', () => { - // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'Stack'); - - // WHEN - Create ApplicationTargetGroup for Lambda without protocol - new elbv2.ApplicationTargetGroup(stack, 'TG', { - targetType: elbv2.TargetType.LAMBDA, - // protocol is missing but should be allowed for Lambda - }); - - // THEN - Should not throw - expect(() => app.synth()).not.toThrow(); - }); - - test('ApplicationTargetGroup requires protocol or port even when targets added later', () => { - // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'Stack'); - const vpc = new ec2.Vpc(stack, 'Vpc'); - - // WHEN - Create ApplicationTargetGroup without protocol or port, then add non-Lambda target - const tg = new elbv2.ApplicationTargetGroup(stack, 'TG', { - vpc, - // Neither protocol nor port specified - this should generate a warning - }); - - // Add a non-Lambda target - tg.addTarget(new elbv2.InstanceTarget('i-1234')); - - // THEN - Should synthesize successfully but show warning - const assembly = app.synth(); - const stackArtifact = assembly.getStackByName(stack.stackName); - const warnings = stackArtifact.messages.filter(m => m.level === 'warning'); - expect(warnings.some(w => w.entry.data.includes('ApplicationTargetGroup protocol and port not specified'))).toBe(true); - }); - test('Can add self-registering target to imported TargetGroup', () => { // GIVEN const vpc = new ec2.Vpc(stack, 'Vpc'); From b9d912175c7c90ac17673b7e965cd7955de6acc4 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Tue, 30 Sep 2025 16:21:29 -0400 Subject: [PATCH 18/19] restore removed tests --- .../test/alb/target-group.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts index 377be425f3b72..c15a13b77479f 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts @@ -5,14 +5,14 @@ import * as cdk from '../../../core'; import * as elbv2 from '../../lib'; import { FakeSelfRegisteringTarget } from '../helpers'; -describe('tests', () => { - let app: cdk.App; - let stack: cdk.Stack; +let app: cdk.App; +let stack: cdk.Stack; +beforeEach(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'Stack'); +}); - beforeEach(() => { - app = new cdk.App(); - stack = new cdk.Stack(app, 'Stack'); - }); +describe('tests', () => { test('Empty target Group without type still requires a VPC', () => { // WHEN new elbv2.ApplicationTargetGroup(stack, 'LB', {}); From 1de95006a2b42e74e6c6c9de98644c484aadda0d Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Tue, 30 Sep 2025 16:21:39 -0400 Subject: [PATCH 19/19] test(elasticloadbalancingv2): Add comprehensive validation tests for ApplicationTargetGroup - Add test for warning when protocol and port are not specified - Verify warning is not shown when port is provided - Ensure no validation error when protocol is specified - Test Lambda target group protocol handling - Add test for protocol/port validation when targets are added later - Improve test coverage for ApplicationTargetGroup configuration scenarios --- .../test/alb/target-group.test.ts | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts index c15a13b77479f..893c9305dee5d 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/target-group.test.ts @@ -71,6 +71,91 @@ describe('tests', () => { expect(() => app.synth()).toThrow(/port\/protocol should not be specified for Lambda targets/); }); + test('ApplicationTargetGroup shows warning when neither protocol nor port specified', () => { + // GIVEN + const vpc = new ec2.Vpc(stack, 'Vpc'); + + // WHEN - Create ApplicationTargetGroup without protocol or port + new elbv2.ApplicationTargetGroup(stack, 'TG', { + vpc, + // Neither protocol nor port specified - this should generate a warning + }); + + // THEN - Should synthesize successfully but show warning + const assembly = app.synth(); + const stackArtifact = assembly.getStackByName(stack.stackName); + const warnings = stackArtifact.messages.filter(m => m.level === 'warning'); + expect(warnings.some(w => w.entry.data && typeof w.entry.data === 'string' && + w.entry.data.includes('ApplicationTargetGroup protocol and port not specified'))).toBe(true); + }); + + test('ApplicationTargetGroup with port but no protocol should not show warning', () => { + // GIVEN + const vpc = new ec2.Vpc(stack, 'Vpc'); + + // WHEN - Create ApplicationTargetGroup without protocol but with port + new elbv2.ApplicationTargetGroup(stack, 'TG', { + vpc, + port: 80, // Providing port satisfies the requirement - protocol will be determined automatically + }); + + // THEN - Should synthesize successfully without warning + const assembly = app.synth(); + const stackArtifact = assembly.getStackByName(stack.stackName); + const warnings = stackArtifact.messages.filter(m => m.level === 'warning'); + expect(warnings.some(w => w.entry.data && typeof w.entry.data === 'string' && + w.entry.data.includes('ApplicationTargetGroup protocol and port not specified'))).toBe(false); + }); + + test('ApplicationTargetGroup with protocol should not throw validation error', () => { + // GIVEN + const vpc = new ec2.Vpc(stack, 'Vpc'); + + // WHEN - Create ApplicationTargetGroup with protocol + new elbv2.ApplicationTargetGroup(stack, 'TG', { + vpc, + port: 80, + protocol: elbv2.ApplicationProtocol.HTTP, + }); + + // THEN - Should not throw + expect(() => app.synth()).not.toThrow(); + }); + + test('ApplicationTargetGroup with Lambda target does not require protocol', () => { + // GIVEN + + // WHEN - Create ApplicationTargetGroup for Lambda without protocol + new elbv2.ApplicationTargetGroup(stack, 'TG', { + targetType: elbv2.TargetType.LAMBDA, + // protocol is missing but should be allowed for Lambda + }); + + // THEN - Should not throw + expect(() => app.synth()).not.toThrow(); + }); + + test('ApplicationTargetGroup requires protocol or port even when targets added later', () => { + // GIVEN + const vpc = new ec2.Vpc(stack, 'Vpc'); + + // WHEN - Create ApplicationTargetGroup without protocol or port, then add non-Lambda target + const tg = new elbv2.ApplicationTargetGroup(stack, 'TG', { + vpc, + // Neither protocol nor port specified - this should generate a warning + }); + + // Add a non-Lambda target + tg.addTarget(new elbv2.InstanceTarget('i-1234')); + + // THEN - Should synthesize successfully but show warning + const assembly = app.synth(); + const stackArtifact = assembly.getStackByName(stack.stackName); + const warnings = stackArtifact.messages.filter(m => m.level === 'warning'); + expect(warnings.some(w => w.entry.data && typeof w.entry.data === 'string' && + w.entry.data.includes('ApplicationTargetGroup protocol and port not specified'))).toBe(true); + }); + test('Can add self-registering target to imported TargetGroup', () => { // GIVEN const vpc = new ec2.Vpc(stack, 'Vpc');