-
Notifications
You must be signed in to change notification settings - Fork 4.1k
aws-ecs-patterns.ApplicationLoadBalancedFargateService: A listener already exists on this port for this load balancer when using redirect_http=True
#34235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Hey @BwL1289, thank you for reporting this issue. I was able to reproduce this issue with CREATE_FAILED | AWS::ElasticLoadBalancingV2::Listener
Resource handler returned message: "A listener already exists on this port for this load balancer 'arn:aws:elasticloadbalancing:us-east-1:...'
(Service: ElasticLoadBalancingV2, Status Code: 400, Request ID: a4d73514-1cc8-4b6a-9fc7-f1da0eff1657)" Reproduction Steps
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns';
export class MyProjectStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = new ec2.Vpc(this, 'MyVpc', {
maxAzs: 2,
natGateways: 1,
});
const cluster = new ecs.Cluster(this, 'MyCluster', {
vpc: vpc,
});
const fargateService =
new ecs_patterns.ApplicationLoadBalancedFargateService(
this,
'MyFargateService',
{
cluster: cluster,
memoryLimitMiB: 512,
cpu: 256,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
},
);
new cdk.CfnOutput(this, 'LoadBalancerDNS', {
value: fargateService.loadBalancer.loadBalancerDnsName,
});
}
}
const certificate = certificatemanager.Certificate.fromCertificateArn(
this,
'Certificate',
'arn:aws:acm:region:account:certificate/certificate-id'
);
const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'MyFargateService', {
cluster: cluster,
memoryLimitMiB: 512,
cpu: 256,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
},
certificate: certificate,
protocol: ApplicationProtocol.HTTPS,
redirectHTTP: true, // This causes the error
});
The issue appears in the if (props.redirectHTTP) {
this.redirectListener = loadBalancer.addListener('PublicRedirectListener', {
protocol: ApplicationProtocol.HTTP,
port: 80,
open: props.openListener ?? true,
defaultAction: ListenerAction.redirect({
port: props.listenerPort?.toString() || '443',
protocol: ApplicationProtocol.HTTPS,
permanent: true,
}),
});
} we may need to strategize this to
Thank you for posting a workaround for this issue: #5583 (comment) |
@BwL1289 Thank you for bringing this to our attention. Yes you are right, I guess we might need a PR to filter out all existing listeners and avoid potential duplicate. Off the top of my head, it should pretty much look like this: // Inside ApplicationLoadBalancedServiceBase constructor, within the
// if (props.redirectHTTP) { ... } block:
if (props.redirectHTTP) {
// Define the redirect action properties
const redirectAction = elbv2.ListenerAction.redirect({
protocol: elbv2.ApplicationProtocol.HTTPS,
port: props.listenerPort?.toString() || '443', // Use the HTTPS listener port or default 443
permanent: true,
});
// --- START REVISED FIX LOGIC ---
let existingHttpListener: elbv2.IApplicationListener | undefined = undefined;
// Check if the load balancer was created by this construct (most reliable case)
// `this._applicationLoadBalancer` holds the owned instance, if created.
if (this._applicationLoadBalancer) {
// Attempt to find an existing listener on port 80.
// Ideally, the ALB construct would have a method like `tryFindListener`.
// If not, iterating `listeners` is possible but requires careful token handling.
existingHttpListener = this._applicationLoadBalancer.listeners.find(listener => {
const cfnListener = listener.node.defaultChild as elbv2.CfnListener;
// Comparing resolved Cfn properties can be brittle due to Tokens.
// This comparison might need refinement or rely on intrinsic functions/Aspects
// for robust token resolution during synthesis if needed.
return cdk.Stack.of(this).resolve(cfnListener.port) === 80 &&
cdk.Stack.of(this).resolve(cfnListener.protocol) === 'HTTP';
});
if (existingHttpListener) {
// If found, modify its default action.
existingHttpListener.addAction('RedirectAction', { // Or modifyDefaultAction if more suitable
action: redirectAction,
});
this.redirectListener = existingHttpListener;
} else {
// If not found (and we own the LB), create a new one.
this.redirectListener = this._applicationLoadBalancer.addListener('PublicRedirectListener', {
protocol: elbv2.ApplicationProtocol.HTTP,
port: 80,
open: props.openListener ?? true, // Use existing openListener prop
defaultAction: redirectAction, // Set the default action to redirect
});
}
} else {
// If the load balancer was imported (props.loadBalancer was provided),
// reliably finding the listener without context lookups is difficult.
// Add a CDK warning annotation to guide the user.
cdk.Annotations.of(this).addWarning(
'Cannot automatically configure redirectHTTP for an imported Load Balancer. '+
'The construct cannot reliably determine if an HTTP listener already exists. '+
'Please configure the redirect manually on the port 80 listener or explicitly pass the listener reference.'
);
// In this case (imported LB), the construct does *not* attempt to add or modify listeners for redirect.
}
// --- END REVISED FIX LOGIC ---
}
We'll bring this up to the team for inputs and we encourage contributions! If you or someone else is interested in implementing the proper fix within the construct (following the revised logic outlined above), a Pull Request would be welcome. The contribution guidelines can be found here. We'll keep this issue open to track progress on the fix. |
@pahud sounds good, this LGTM. I'd make a PR but my TS skills are shaky at best. |
Describe the feature
Similar to #13759 and #15894,
redirect_http=True
should not unconditionally create an HTTP listener.It should be possible to pass in an existing http listener.
Use Case
Allow passing in an existing http listener when using
redirect_http=True
. If you've already deployed your stack and try to useredirect_http=True
, you'll get the following error:Proposed Solution
Allow passing in an existing http listener when using
redirect_http=True
Other Information
No response
Acknowledgements
CDK version used
2.180.0
Environment details (OS name and version, etc.)
MacOS
The text was updated successfully, but these errors were encountered: