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..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 @@ -404,6 +404,14 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat this.setAttribute('load_balancing.algorithm.anomaly_mitigation', props.enableAnomalyMitigation ? 'on' : 'off'); } } + + // 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 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.', + ); + } } public get metrics(): IApplicationTargetGroupMetrics { @@ -614,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 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');