Skip to content

Commit

Permalink
feat(elasticloadbalancingv2): add load balancer lookups (aws#11089)
Browse files Browse the repository at this point in the history
Adds `fromLookup()` methods to both Application and Network load balancers as well as their listeners.

Closes aws#11088 

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
misterjoshua authored Nov 9, 2020
1 parent 550dd99 commit 0153028
Show file tree
Hide file tree
Showing 29 changed files with 2,606 additions and 45 deletions.
6 changes: 5 additions & 1 deletion allowed-breaking-changes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
# and that won't typecheck if Manifest.load() adds a union arm and now returns A | B | C.
change-return-type:@aws-cdk/cloud-assembly-schema.Manifest.load

# Adding any new context queries will add to the ContextQueryProperties type,
# which changes the signature of MissingContext.
weakened:@aws-cdk/cloud-assembly-schema.MissingContext

removed:@aws-cdk/core.BootstraplessSynthesizer.DEFAULT_ASSET_PUBLISHING_ROLE_ARN
removed:@aws-cdk/core.DefaultStackSynthesizer.DEFAULT_ASSET_PUBLISHING_ROLE_ARN
removed:@aws-cdk/core.DefaultStackSynthesizerProps.assetPublishingExternalId
Expand Down Expand Up @@ -47,4 +51,4 @@ incompatible-argument:@aws-cdk/aws-ecs.Ec2TaskDefinition.addVolume
incompatible-argument:@aws-cdk/aws-ecs.FargateTaskDefinition.<initializer>
incompatible-argument:@aws-cdk/aws-ecs.FargateTaskDefinition.addVolume
incompatible-argument:@aws-cdk/aws-ecs.TaskDefinition.<initializer>
incompatible-argument:@aws-cdk/aws-ecs.TaskDefinition.addVolume
incompatible-argument:@aws-cdk/aws-ecs.TaskDefinition.addVolume
26 changes: 25 additions & 1 deletion packages/@aws-cdk/aws-ec2/lib/security-group.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Annotations, IResource, Lazy, Names, Resource, ResourceProps, Stack, Token } from '@aws-cdk/core';
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
import { Annotations, ContextProvider, IResource, Lazy, Names, Resource, ResourceProps, Stack, Token } from '@aws-cdk/core';
import * as cxapi from '@aws-cdk/cx-api';
import { Construct } from 'constructs';
import { Connections } from './connections';
import { CfnSecurityGroup, CfnSecurityGroupEgress, CfnSecurityGroupIngress } from './ec2.generated';
Expand Down Expand Up @@ -294,6 +296,28 @@ export interface SecurityGroupImportOptions {
* ```
*/
export class SecurityGroup extends SecurityGroupBase {
/**
* Look up a security group by id.
*/
public static fromLookup(scope: Construct, id: string, securityGroupId: string) {
if (Token.isUnresolved(securityGroupId)) {
throw new Error('All arguments to look up a security group must be concrete (no Tokens)');
}

const attributes: cxapi.SecurityGroupContextResponse = ContextProvider.getValue(scope, {
provider: cxschema.ContextProvider.SECURITY_GROUP_PROVIDER,
props: { securityGroupId },
dummyValue: {
securityGroupId: 'sg-12345',
allowAllOutbound: true,
} as cxapi.SecurityGroupContextResponse,
}).value;

return SecurityGroup.fromSecurityGroupId(scope, id, attributes.securityGroupId, {
allowAllOutbound: attributes.allowAllOutbound,
mutable: true,
});
}

/**
* Import an existing security group into this app.
Expand Down
19 changes: 18 additions & 1 deletion packages/@aws-cdk/aws-ec2/test/security-group.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, haveResource, not } from '@aws-cdk/assert';
import { Intrinsic, Lazy, Stack, Token } from '@aws-cdk/core';
import { App, Intrinsic, Lazy, Stack, Token } from '@aws-cdk/core';
import { nodeunitShim, Test } from 'nodeunit-shim';
import { Peer, Port, SecurityGroup, Vpc } from '../lib';

Expand Down Expand Up @@ -293,4 +293,21 @@ nodeunitShim({
test.done();
},
},

'can look up a security group'(test: Test) {
const app = new App();
const stack = new Stack(app, 'stack', {
env: {
account: '1234',
region: 'us-east-1',
},
});

const securityGroup = SecurityGroup.fromLookup(stack, 'stack', 'sg-1234');

test.equal(securityGroup.securityGroupId, 'sg-12345');
test.equal(securityGroup.allowAllOutbound, true);

test.done();
},
});
88 changes: 88 additions & 0 deletions packages/@aws-cdk/aws-elasticloadbalancingv2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,91 @@ case for ECS Services for example), take a resource dependency on
// has been associated with the LoadBalancer, before 'resource' is created.
resourced.addDependency(targetGroup.loadBalancerDependency());
```

## Looking up Load Balancers and Listeners

You may look up load balancers and load balancer listeners by using one of the
following lookup methods:

- `ApplicationLoadBalancer.fromlookup(options)` - Look up an application load
balancer.
- `ApplicationListener.fromLookup(options)` - Look up an application load
balancer listener.
- `NetworkLoadBalancer.fromLookup(options)` - Look up a network load balancer.
- `NetworkListener.fromLookup(options)` - Look up a network load balancer
listener.

### Load Balancer lookup options

You may look up a load balancer by ARN or by associated tags. When you look a
load balancer up by ARN, that load balancer will be returned unless CDK detects
that the load balancer is of the wrong type. When you look up a load balancer by
tags, CDK will return the load balancer matching all specified tags. If more
than one load balancer matches, CDK will throw an error requesting that you
provide more specific criteria.

**Look up a Application Load Balancer by ARN**
```ts
const loadBalancer = ApplicationLoadBalancer.fromLookup(stack, 'ALB', {
loadBalancerArn: YOUR_ALB_ARN,
});
```

**Look up an Application Load Balancer by tags**
```ts
const loadBalancer = ApplicationLoadBalancer.fromLookup(stack, 'ALB', {
loadBalancerTags: {
// Finds a load balancer matching all tags.
some: 'tag',
someother: 'tag',
},
});
```

## Load Balancer Listener lookup options

You may look up a load balancer listener by the following criteria:

- Associated load balancer ARN
- Associated load balancer tags
- Listener ARN
- Listener port
- Listener protocol

The lookup method will return the matching listener. If more than one listener
matches, CDK will throw an error requesting that you specify additional
criteria.

**Look up a Listener by associated Load Balancer, Port, and Protocol**

```ts
const listener = ApplicationListener.fromLookup(stack, 'ALBListener', {
loadBalancerArn: YOUR_ALB_ARN,
listenerProtocol: ApplicationProtocol.HTTPS,
listenerPort: 443,
});
```

**Look up a Listener by associated Load Balancer Tag, Port, and Protocol**

```ts
const listener = ApplicationListener.fromLookup(stack, 'ALBListener', {
loadBalancerTags: {
Cluster: 'MyClusterName',
},
listenerProtocol: ApplicationProtocol.HTTPS,
listenerPort: 443,
});
```

**Look up a Network Listener by associated Load Balancer Tag, Port, and Protocol**

```ts
const listener = NetworkListener.fromLookup(stack, 'ALBListener', {
loadBalancerTags: {
Cluster: 'MyClusterName',
},
listenerProtocol: Protocol.TCP,
listenerPort: 12345,
});
```
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as ec2 from '@aws-cdk/aws-ec2';
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
import { Duration, IResource, Lazy, Resource, Token } from '@aws-cdk/core';
import * as cxapi from '@aws-cdk/cx-api';
import { Construct } from 'constructs';
import { BaseListener } from '../shared/base-listener';
import { BaseListener, BaseListenerLookupOptions } from '../shared/base-listener';
import { HealthCheck } from '../shared/base-target-group';
import { ApplicationProtocol, IpAddressType, SslPolicy } from '../shared/enums';
import { IListenerCertificate, ListenerCertificate } from '../shared/listener-certificate';
Expand Down Expand Up @@ -106,12 +108,53 @@ export interface ApplicationListenerProps extends BaseApplicationListenerProps {
readonly loadBalancer: IApplicationLoadBalancer;
}

/**
* Options for ApplicationListener lookup
*/
export interface ApplicationListenerLookupOptions extends BaseListenerLookupOptions {
/**
* ARN of the listener to look up
* @default - does not filter by listener arn
*/
readonly listenerArn?: string;

/**
* Filter listeners by listener protocol
* @default - does not filter by listener protocol
*/
readonly listenerProtocol?: ApplicationProtocol;
}

/**
* Define an ApplicationListener
*
* @resource AWS::ElasticLoadBalancingV2::Listener
*/
export class ApplicationListener extends BaseListener implements IApplicationListener {
/**
* Look up an ApplicationListener.
*/
public static fromLookup(scope: Construct, id: string, options: ApplicationListenerLookupOptions): IApplicationListener {
if (Token.isUnresolved(options.listenerArn)) {
throw new Error('All arguments to look up a load balancer listener must be concrete (no Tokens)');
}

let listenerProtocol: cxschema.LoadBalancerListenerProtocol | undefined;
switch (options.listenerProtocol) {
case ApplicationProtocol.HTTP: listenerProtocol = cxschema.LoadBalancerListenerProtocol.HTTP; break;
case ApplicationProtocol.HTTPS: listenerProtocol = cxschema.LoadBalancerListenerProtocol.HTTPS; break;
}

const props = BaseListener._queryContextProvider(scope, {
userOptions: options,
loadBalancerType: cxschema.LoadBalancerType.APPLICATION,
listenerArn: options.listenerArn,
listenerProtocol,
});

return new LookedUpApplicationListener(scope, id, props);
}

/**
* Import an existing listener
*/
Expand Down Expand Up @@ -517,36 +560,28 @@ export interface ApplicationListenerAttributes {
readonly securityGroupAllowsAllOutbound?: boolean;
}

class ImportedApplicationListener extends Resource implements IApplicationListener {
public readonly connections: ec2.Connections;
abstract class ExternalApplicationListener extends Resource implements IApplicationListener {
/**
* Connections object.
*/
public abstract readonly connections: ec2.Connections;

/**
* ARN of the listener
*/
public readonly listenerArn: string;
public abstract readonly listenerArn: string;

constructor(scope: Construct, id: string, props: ApplicationListenerAttributes) {
constructor(scope: Construct, id: string) {
super(scope, id);
}

this.listenerArn = props.listenerArn;

const defaultPort = props.defaultPort !== undefined ? ec2.Port.tcp(props.defaultPort) : undefined;

let securityGroup: ec2.ISecurityGroup;
if (props.securityGroup) {
securityGroup = props.securityGroup;
} else if (props.securityGroupId) {
securityGroup = ec2.SecurityGroup.fromSecurityGroupId(scope, 'SecurityGroup', props.securityGroupId, {
allowAllOutbound: props.securityGroupAllowsAllOutbound,
});
} else {
throw new Error('Either `securityGroup` or `securityGroupId` must be specified to import an application listener.');
}

this.connections = new ec2.Connections({
securityGroups: [securityGroup],
defaultPort,
});
/**
* Register that a connectable that has been added to this load balancer.
*
* Don't call this directly. It is called by ApplicationTargetGroup.
*/
public registerConnectable(connectable: ec2.IConnectable, portRange: ec2.Port): void {
this.connections.allowTo(connectable, portRange, 'Load balancer to target');
}

/**
Expand Down Expand Up @@ -599,14 +634,55 @@ class ImportedApplicationListener extends Resource implements IApplicationListen
// eslint-disable-next-line max-len
throw new Error('Can only call addTargets() when using a constructed ApplicationListener; construct a new TargetGroup and use addTargetGroup.');
}
}

/**
* Register that a connectable that has been added to this load balancer.
*
* Don't call this directly. It is called by ApplicationTargetGroup.
*/
public registerConnectable(connectable: ec2.IConnectable, portRange: ec2.Port): void {
this.connections.allowTo(connectable, portRange, 'Load balancer to target');
/**
* An imported application listener.
*/
class ImportedApplicationListener extends ExternalApplicationListener {
public readonly listenerArn: string;
public readonly connections: ec2.Connections;

constructor(scope: Construct, id: string, props: ApplicationListenerAttributes) {
super(scope, id);

this.listenerArn = props.listenerArn;
const defaultPort = props.defaultPort !== undefined ? ec2.Port.tcp(props.defaultPort) : undefined;

let securityGroup: ec2.ISecurityGroup;
if (props.securityGroup) {
securityGroup = props.securityGroup;
} else if (props.securityGroupId) {
securityGroup = ec2.SecurityGroup.fromSecurityGroupId(scope, 'SecurityGroup', props.securityGroupId, {
allowAllOutbound: props.securityGroupAllowsAllOutbound,
});
} else {
throw new Error('Either `securityGroup` or `securityGroupId` must be specified to import an application listener.');
}

this.connections = new ec2.Connections({
securityGroups: [securityGroup],
defaultPort,
});
}
}

class LookedUpApplicationListener extends ExternalApplicationListener {
public readonly listenerArn: string;
public readonly connections: ec2.Connections;

constructor(scope: Construct, id: string, props: cxapi.LoadBalancerListenerContextResponse) {
super(scope, id);

this.listenerArn = props.listenerArn;
this.connections = new ec2.Connections({
defaultPort: ec2.Port.tcp(props.listenerPort),
});

for (const securityGroupId of props.securityGroupIds) {
const securityGroup = ec2.SecurityGroup.fromLookup(this, `SecurityGroup-${securityGroupId}`, securityGroupId);
this.connections.addSecurityGroup(securityGroup);
}
}
}

Expand Down
Loading

0 comments on commit 0153028

Please sign in to comment.