From c3ee41366b3f0a59864decd47d2feea9849fd095 Mon Sep 17 00:00:00 2001 From: Lapderk Date: Mon, 9 Mar 2020 14:53:50 +0100 Subject: [PATCH] feat(elbv2): support pathpattern array (#6558) * Adding support for an array of path patterns for application listener rule config. Closes #6497 * Introduce `pathPatterns` prop for listener rule * Introduce `pathPatterns` prop for listener rule * Deperecate `pathPattern`. Co-authored-by: Derk Schooltink Co-authored-by: Rico Huijbers Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../aws-elasticloadbalancingv2/README.md | 17 ++++-- .../lib/alb/application-listener-rule.ts | 28 +++++++--- .../lib/alb/application-listener.ts | 22 ++++++-- .../test/alb/test.listener.ts | 55 +++++++++++++++++++ 4 files changed, 104 insertions(+), 18 deletions(-) diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md b/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md index 89f5712782b74..4d96b83dce785 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md @@ -73,23 +73,28 @@ listener.addFixedResponse('Fixed', { #### Conditions It's possible to route traffic to targets based on conditions in the incoming -HTTP request. Path- and host-based conditions are supported. For example, -the following will route requests to the indicated AutoScalingGroup -only if the requested host in the request is `example.com`: +HTTP request. Path- and host-based conditions are supported. For example, the +following will route requests to the indicated AutoScalingGroup only if the +requested host in the request is either for `example.com/ok` or +`example.com/path`: ```ts listener.addTargets('Example.Com Fleet', { priority: 10, + pathPatterns: ['/ok', '/path'], hostHeader: 'example.com', port: 8080, targets: [asg] }); ``` -`priority` is a required field when you add targets with conditions. The lowest -number wins. +A target with a condition contains either `pathPatterns` or `hostHeader`, or +both. If both are specified, both conditions must be met for the requests to +be routed to the given target. `priority` is a required field when you add +targets with conditions. The lowest number wins. -Every listener must have at least one target without conditions. +Every listener must have at least one target without conditions, which is +where all requests that didn't match any of the conditions will be sent. ### Defining a Network Load Balancer diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts index abfeae0b015e9..a496e013f0596 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts @@ -54,13 +54,21 @@ export interface BaseApplicationListenerRuleProps { /** * Rule applies if the requested path matches the given path pattern * - * May contain up to three '*' wildcards. - * * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#path-conditions - * * @default - No path condition. + * @deprecated Use `pathPatterns` instead. */ readonly pathPattern?: string; + + /** + * Rule applies if the requested path matches any of the given patterns. + * + * Paths may contain up to three '*' wildcards. + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#path-conditions + * @default - No path conditions. + */ + readonly pathPatterns?: string[]; } /** @@ -169,8 +177,9 @@ export class ApplicationListenerRule extends cdk.Construct { constructor(scope: cdk.Construct, id: string, props: ApplicationListenerRuleProps) { super(scope, id); - if (!props.hostHeader && !props.pathPattern) { - throw new Error(`At least one of 'hostHeader' or 'pathPattern' is required when defining a load balancing rule.`); + const hasPathPatterns = props.pathPatterns || props.pathPattern; + if (!props.hostHeader && !hasPathPatterns) { + throw new Error(`At least one of 'hostHeader', 'pathPattern' or 'pathPatterns' is required when defining a load balancing rule.`); } const possibleActions: Array = ['targetGroups', 'fixedResponse', 'redirectResponse']; @@ -195,8 +204,13 @@ export class ApplicationListenerRule extends cdk.Construct { if (props.hostHeader) { this.setCondition('host-header', [props.hostHeader]); } - if (props.pathPattern) { - this.setCondition('path-pattern', [props.pathPattern]); + + if (hasPathPatterns) { + if (props.pathPattern && props.pathPatterns) { + throw new Error('Both `pathPatterns` and `pathPattern` are specified, specify only one'); + } + const pathPattern = props.pathPattern ? [props.pathPattern] : props.pathPatterns; + this.setCondition('path-pattern', pathPattern); } (props.targetGroups || []).forEach(this.addTargetGroup.bind(this)); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts index f72d35d3ae28d..3864a2e3b2f14 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -209,6 +209,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis listener: this, hostHeader: props.hostHeader, pathPattern: props.pathPattern, + pathPatterns: props.pathPatterns, priority: props.priority, targetGroups: props.targetGroups }); @@ -252,6 +253,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis this.addTargetGroups(id, { hostHeader: props.hostHeader, pathPattern: props.pathPattern, + pathPatterns: props.pathPatterns, priority: props.priority, targetGroups: [group], }); @@ -480,9 +482,7 @@ class ImportedApplicationListener extends Resource implements IApplicationListen * At least one TargetGroup must be added without conditions. */ public addTargetGroups(id: string, props: AddApplicationTargetGroupsProps): void { - if ((props.hostHeader !== undefined || props.pathPattern !== undefined) !== (props.priority !== undefined)) { - throw new Error(`Setting 'pathPattern' or 'hostHeader' also requires 'priority', and vice versa`); - } + checkAddRuleProps(props); if (props.priority !== undefined) { // New rule @@ -562,10 +562,22 @@ export interface AddRuleProps { * Requires that priority is set. * * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#path-conditions - * * @default No path condition + * @deprecated Use `pathPatterns` instead. */ readonly pathPattern?: string; + + /** + * Rule applies if the requested path matches any of the given patterns. + * + * May contain up to three '*' wildcards. + * + * Requires that priority is set. + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#path-conditions + * @default - No path condition. + */ + readonly pathPatterns?: string[]; } /** @@ -667,7 +679,7 @@ export interface AddRedirectResponseProps extends AddRuleProps, RedirectResponse } function checkAddRuleProps(props: AddRuleProps) { - if ((props.hostHeader !== undefined || props.pathPattern !== undefined) !== (props.priority !== undefined)) { + if ((props.hostHeader !== undefined || props.pathPattern !== undefined || props.pathPatterns !== undefined) !== (props.priority !== undefined)) { throw new Error(`Setting 'pathPattern' or 'hostHeader' also requires 'priority', and vice versa`); } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts index 3704768515cf5..2810075f6c787 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts @@ -937,6 +937,61 @@ export = { test.done(); }, + + 'Can add multiple path patterns to listener rule'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + + // WHEN + const listener = lb.addListener('Listener', { + port: 443, + certificateArns: ['cert1', 'cert2'], + defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })] + }); + + listener.addTargets('Target1', { + priority: 10, + pathPatterns: ['/test/path/1', '/test/path/2'] + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerRule', { + Priority: 10, + Conditions: [ + { + Field: 'path-pattern', + Values: ['/test/path/1', '/test/path/2'] + } + ] + })); + + test.done(); + }, + + 'Cannot add pathPattern and pathPatterns to listener rule'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + + // WHEN + const listener = lb.addListener('Listener', { + port: 443, + certificateArns: ['cert1', 'cert2'], + defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })] + }); + + // THEN + test.throws(() => listener.addTargets('Target1', { + priority: 10, + pathPatterns: ['/test/path/1', '/test/path/2'], + pathPattern: '/test/path/3' + }), Error, `At least one of 'hostHeader', 'pathPattern' or 'pathPatterns' is required when defining a load balancing rule.`); + + test.done(); + }, }; class ResourceWithLBDependency extends cdk.CfnResource {