diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts index 0ca41490969d8..583ca06435c09 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts @@ -276,12 +276,9 @@ export class AppMeshExtension extends ServiceExtension { // and other similar behaviors. this.virtualRouter = new appmesh.VirtualRouter(this.scope, `${this.parentService.id}-virtual-router`, { mesh: this.mesh, - listener: { - portMapping: { - port: containerextension.trafficPort, - protocol: this.protocol, - }, - }, + listeners: [ + this.virtualRouterListener(containerextension.trafficPort), + ], virtualRouterName: `${this.parentService.id}`, }); @@ -331,4 +328,13 @@ export class AppMeshExtension extends ServiceExtension { // nodes from the other service. this.virtualNode.addBackends(otherAppMesh.virtualService); } + + private virtualRouterListener(port: number): appmesh.VirtualRouterListener { + switch (this.protocol) { + case appmesh.Protocol.HTTP: return appmesh.VirtualRouterListener.http(port); + case appmesh.Protocol.HTTP2: return appmesh.VirtualRouterListener.http2(port); + case appmesh.Protocol.GRPC: return appmesh.VirtualRouterListener.grpc(port); + case appmesh.Protocol.TCP: return appmesh.VirtualRouterListener.tcp(port); + } + } } diff --git a/packages/@aws-cdk/aws-appmesh/README.md b/packages/@aws-cdk/aws-appmesh/README.md index 00d70bd8b0888..9c0a4b7f1da2e 100644 --- a/packages/@aws-cdk/aws-appmesh/README.md +++ b/packages/@aws-cdk/aws-appmesh/README.md @@ -53,22 +53,21 @@ const mesh = new Mesh(stack, 'AppMesh', { ## Adding VirtualRouters -The `Mesh` needs `VirtualRouters` as logical units to route to `VirtualNodes`. +The _Mesh_ needs _VirtualRouters_ as logical units to route requests to _VirtualNodes_. -Virtual routers handle traffic for one or more virtual services within your mesh. After you create a virtual router, you can create and associate routes for your virtual router that direct incoming requests to different virtual nodes. +Virtual routers handle traffic for one or more virtual services within your mesh. +After you create a virtual router, you can create and associate routes to your virtual router that direct incoming requests to different virtual nodes. ```typescript const router = mesh.addVirtualRouter('router', { - listener: { - portMapping: { - port: 8081, - protocol: Protocol.HTTP, - } - } + listeners: [ appmesh.VirtualRouterListener.http(8080) ], }); ``` -The router can also be created using the constructor and passing in the mesh instead of calling the addVirtualRouter() method for the mesh. +The router can also be created using the constructor and passing in the mesh instead of calling the `addVirtualRouter()` method for the mesh. +The same pattern applies to all constructs within the appmesh library, for any mesh.addXZY method, a new constuctor can also be used. +This is particularly useful for cross stack resources are required. +Where creating the `mesh` as part of an infrastructure stack and creating the `resources` such as `nodes` is more useful to keep in the application stack. ```typescript const mesh = new Mesh(stack, 'AppMesh', { @@ -78,18 +77,15 @@ const mesh = new Mesh(stack, 'AppMesh', { const router = new VirtualRouter(stack, 'router', { mesh, // notice that mesh is a required property when creating a router with a new statement - listener: { - portMapping: { - port: 8081, - protocol: Protocol.HTTP, - } + listeners: [ appmesh.VirtualRouterListener.http(8081) ] } }); ``` -The listener protocol can be either `HTTP` or `TCP`. - -The same pattern applies to all constructs within the appmesh library, for any mesh.addXZY method, a new constuctor can also be used. This is particularly useful for cross stack resources are required. Where creating the `mesh` as part of an infrastructure stack and creating the `resources` such as `nodes` is more useful to keep in the application stack. +The _VirtualRouterListener_ class provides an easy interface for defining new protocol specific listeners. +The `http()`, `http2()`, `grpc()` and `tcp()` methods are available for use. +They accept a single port parameter, that is used to define what port to match requests on. +The port parameter can be omitted, and it will default to port 8080. ## Adding VirtualService diff --git a/packages/@aws-cdk/aws-appmesh/lib/index.ts b/packages/@aws-cdk/aws-appmesh/lib/index.ts index 69cdfd8d2e3c6..d95c017c2071e 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/index.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/index.ts @@ -5,6 +5,7 @@ export * from './route'; export * from './shared-interfaces'; export * from './virtual-node'; export * from './virtual-router'; +export * from './virtual-router-listener'; export * from './virtual-service'; export * from './virtual-gateway'; export * from './virtual-gateway-listener'; diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-router-listener.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-router-listener.ts new file mode 100644 index 0000000000000..7a5e867d0b7c9 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-router-listener.ts @@ -0,0 +1,82 @@ +import * as cdk from '@aws-cdk/core'; +import { CfnVirtualRouter } from './appmesh.generated'; +import { Protocol } from './shared-interfaces'; + +/** + * Properties for a VirtualRouter listener + */ +export interface VirtualRouterListenerConfig { + /** + * Single listener config for a VirtualRouter + */ + readonly listener: CfnVirtualRouter.VirtualRouterListenerProperty; +} + +/** + * Represents the properties needed to define listeners for a VirtualRouter + */ +export abstract class VirtualRouterListener { + /** + * Returns an HTTP Listener for a VirtualRouter + * + * @param port the optional port of the listener, 8080 by default + */ + public static http(port?: number): VirtualRouterListener { + return new VirtualRouterListenerImpl(Protocol.HTTP, port); + } + + /** + * Returns an HTTP2 Listener for a VirtualRouter + * + * @param port the optional port of the listener, 8080 by default + */ + public static http2(port?: number): VirtualRouterListener { + return new VirtualRouterListenerImpl(Protocol.HTTP2, port); + } + + /** + * Returns a GRPC Listener for a VirtualRouter + * + * @param port the optional port of the listener, 8080 by default + */ + public static grpc(port?: number): VirtualRouterListener { + return new VirtualRouterListenerImpl(Protocol.GRPC, port); + } + + /** + * Returns a TCP Listener for a VirtualRouter + * + * @param port the optional port of the listener, 8080 by default + */ + public static tcp(port?: number): VirtualRouterListener { + return new VirtualRouterListenerImpl(Protocol.TCP, port); + } + + /** + * Called when the VirtualRouterListener type is initialized. Can be used to enforce + * mutual exclusivity + */ + public abstract bind(scope: cdk.Construct): VirtualRouterListenerConfig; +} + +class VirtualRouterListenerImpl extends VirtualRouterListener { + private readonly protocol: Protocol; + private readonly port: number; + + constructor(protocol: Protocol, port?: number) { + super(); + this.protocol = protocol; + this.port = port ?? 8080; + } + + bind(_scope: cdk.Construct): VirtualRouterListenerConfig { + return { + listener: { + portMapping: { + port: this.port, + protocol: this.protocol, + }, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts index 2b416427ae389..65c3921a844fd 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts @@ -3,7 +3,7 @@ import { Construct } from 'constructs'; import { CfnVirtualRouter } from './appmesh.generated'; import { IMesh, Mesh } from './mesh'; import { Route, RouteBaseProps } from './route'; -import { PortMapping, Protocol } from './shared-interfaces'; +import { VirtualRouterListener } from './virtual-router-listener'; /** * Interface which all VirtualRouter based classes MUST implement @@ -43,7 +43,7 @@ export interface VirtualRouterBaseProps { * * @default - A listener on HTTP port 8080 */ - readonly listener?: Listener; + readonly listeners?: VirtualRouterListener[]; /** * The name of the VirtualRouter @@ -53,16 +53,6 @@ export interface VirtualRouterBaseProps { readonly virtualRouterName?: string; } -/** - * A single listener for - */ -export interface Listener { - /** - * Listener port for the VirtualRouter - */ - readonly portMapping: PortMapping; -} - abstract class VirtualRouterBase extends cdk.Resource implements IVirtualRouter { /** * The name of the VirtualRouter @@ -155,8 +145,11 @@ export class VirtualRouter extends VirtualRouterBase { }); this.mesh = props.mesh; - - this.addListener(props.listener || { portMapping: { port: 8080, protocol: Protocol.HTTP } }); + if (props.listeners && props.listeners.length) { + props.listeners.forEach(listener => this.addListener(listener)); + } else { + this.addListener(VirtualRouterListener.http()); + } const router = new CfnVirtualRouter(this, 'Resource', { virtualRouterName: this.physicalName, @@ -177,10 +170,8 @@ export class VirtualRouter extends VirtualRouterBase { /** * Add port mappings to the router */ - private addListener(listener: Listener) { - this.listeners.push({ - portMapping: listener.portMapping, - }); + private addListener(listener: VirtualRouterListener) { + this.listeners.push(listener.bind(this).listener); } } diff --git a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts index 322ae1cc608d6..3de6eb20c77d2 100644 --- a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts @@ -19,12 +19,9 @@ const namespace = new cloudmap.PrivateDnsNamespace(stack, 'test-namespace', { const mesh = new appmesh.Mesh(stack, 'mesh'); const router = mesh.addVirtualRouter('router', { - listener: { - portMapping: { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, - }, + listeners: [ + appmesh.VirtualRouterListener.http(), + ], }); const virtualService = mesh.addVirtualService('service', { diff --git a/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts b/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts index f8e6ebcbb716d..690dce4a08ddc 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts @@ -66,14 +66,7 @@ export = { meshName: 'test-mesh', }); - mesh.addVirtualRouter('router', { - listener: { - portMapping: { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, - }, - }); + mesh.addVirtualRouter('router'); // THEN expect(stack).to( @@ -147,12 +140,9 @@ export = { }); const testRouter = mesh.addVirtualRouter('router', { - listener: { - portMapping: { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, - }, + listeners: [ + appmesh.VirtualRouterListener.http(), + ], }); // THEN @@ -178,12 +168,9 @@ export = { }); const testRouter = mesh.addVirtualRouter('test-router', { - listener: { - portMapping: { - port: 8080, - protocol: appmesh.Protocol.HTTP, - }, - }, + listeners: [ + appmesh.VirtualRouterListener.http(), + ], }); mesh.addVirtualService('service', { diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts index dff7e4321f1f1..72510c83c4de2 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts @@ -5,6 +5,88 @@ import { Test } from 'nodeunit'; import * as appmesh from '../lib'; export = { + 'When creating a VirtualRouter': { + 'should have appropriate defaults'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + // WHEN + mesh.addVirtualRouter('http-router-listener'); + + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualRouter', { + VirtualRouterName: 'meshhttprouterlistenerF57BCB2F', + Spec: { + Listeners: [ + { + PortMapping: { + Port: 8080, + Protocol: appmesh.Protocol.HTTP, + }, + }, + ], + }, + })); + test.done(); + }, + 'should have protocol variant listeners'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + // WHEN + mesh.addVirtualRouter('http-router-listener', { + listeners: [ + appmesh.VirtualRouterListener.http(), + ], + virtualRouterName: 'http-router-listener', + }); + + mesh.addVirtualRouter('http2-router-listener', { + listeners: [ + appmesh.VirtualRouterListener.http2(), + ], + virtualRouterName: 'http2-router-listener', + }); + + mesh.addVirtualRouter('grpc-router-listener', { + listeners: [ + appmesh.VirtualRouterListener.grpc(), + ], + virtualRouterName: 'grpc-router-listener', + }); + + mesh.addVirtualRouter('tcp-router-listener', { + listeners: [ + appmesh.VirtualRouterListener.tcp(), + ], + virtualRouterName: 'tcp-router-listener', + }); + + // THEN + const expectedPorts = [appmesh.Protocol.HTTP, appmesh.Protocol.HTTP2, appmesh.Protocol.GRPC, appmesh.Protocol.TCP]; + expectedPorts.forEach(protocol => { + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualRouter', { + VirtualRouterName: `${protocol}-router-listener`, + Spec: { + Listeners: [ + { + PortMapping: { + Port: 8080, + Protocol: protocol, + }, + }, + ], + }, + })); + }); + + test.done(); + }, + }, + 'When adding route to existing VirtualRouter': { 'should create route resource'(test: Test) { // GIVEN