diff --git a/src/constructs/loadbalancing/clb.test.ts b/src/constructs/loadbalancing/clb.test.ts index bca8e7e03..1d6a5ee09 100644 --- a/src/constructs/loadbalancing/clb.test.ts +++ b/src/constructs/loadbalancing/clb.test.ts @@ -5,6 +5,7 @@ import { Stack } from "@aws-cdk/core"; import { simpleGuStackForTesting } from "../../../test/utils/simple-gu-stack"; import type { SynthedStack } from "../../../test/utils/synthed-stack"; import { GuClassicLoadBalancer } from "../loadbalancing"; +import { GuHttpsClassicLoadBalancer } from "./clb"; describe("The GuClassicLoadBalancer class", () => { const vpc = Vpc.fromVpcAttributes(new Stack(), "VPC", { @@ -116,3 +117,99 @@ describe("The GuClassicLoadBalancer class", () => { }); }); }); + +describe("The GuHttpsClassicLoadBalancer class", () => { + const vpc = Vpc.fromVpcAttributes(new Stack(), "VPC", { + vpcId: "test", + availabilityZones: [""], + publicSubnetIds: [""], + privateSubnetIds: [""], + }); + + test("uses default listener values", () => { + const stack = simpleGuStackForTesting(); + new GuHttpsClassicLoadBalancer(stack, "HttpsClassicLoadBalancer", { + vpc, + }); + + expect(stack).toHaveResource("AWS::ElasticLoadBalancing::LoadBalancer", { + Listeners: [ + { + InstancePort: "9000", + InstanceProtocol: "http", + LoadBalancerPort: "443", + Protocol: "https", + SSLCertificateId: { + Ref: "CertificateARN", + }, + }, + ], + }); + }); + + test("adds the CertificateARN parameter if no value provided", () => { + const stack = simpleGuStackForTesting(); + new GuHttpsClassicLoadBalancer(stack, "HttpsClassicLoadBalancer", { + vpc, + }); + + const json = SynthUtils.toCloudFormation(stack) as SynthedStack; + + expect(json.Parameters.CertificateARN).toEqual({ + AllowedPattern: "arn:aws:[a-z0-9]*:[a-z0-9\\-]*:[0-9]{12}:.*", + Description: "Certificate ARN for ELB", + ConstraintDescription: "Must be a valid ARN, eg: arn:partition:service:region:account-id:resource-id", + Type: "String", + }); + }); + + test("uses the certificate id provided", () => { + const stack = simpleGuStackForTesting(); + new GuHttpsClassicLoadBalancer(stack, "HttpsClassicLoadBalancer", { + vpc, + listener: { + sslCertificateId: "certificateId", + }, + }); + + expect(stack).toHaveResource("AWS::ElasticLoadBalancing::LoadBalancer", { + Listeners: [ + { + InstancePort: "9000", + InstanceProtocol: "http", + LoadBalancerPort: "443", + Protocol: "https", + SSLCertificateId: "certificateId", + }, + ], + }); + + const json = SynthUtils.toCloudFormation(stack) as SynthedStack; + + expect(Object.keys(json.Parameters)).not.toContain("CertificateARN"); + }); + + test("merges any listener values provided", () => { + const stack = simpleGuStackForTesting(); + new GuHttpsClassicLoadBalancer(stack, "HttpsClassicLoadBalancer", { + vpc, + listener: { + internalPort: 3000, + }, + }); + + expect(stack).toHaveResource("AWS::ElasticLoadBalancing::LoadBalancer", { + Listeners: [ + { + InstancePort: "3000", + InstanceProtocol: "http", + LoadBalancerPort: "443", + Protocol: "https", + SSLCertificateId: { + Ref: "CertificateARN", + }, + }, + ], + }); + }); +}); diff --git a/src/constructs/loadbalancing/clb.ts b/src/constructs/loadbalancing/clb.ts index 004688074..4a9830a30 100644 --- a/src/constructs/loadbalancing/clb.ts +++ b/src/constructs/loadbalancing/clb.ts @@ -1,7 +1,13 @@ -import type { CfnLoadBalancer, HealthCheck, LoadBalancerProps } from "@aws-cdk/aws-elasticloadbalancing"; +import type { + CfnLoadBalancer, + HealthCheck, + LoadBalancerListener, + LoadBalancerProps, +} from "@aws-cdk/aws-elasticloadbalancing"; import { LoadBalancer, LoadBalancingProtocol } from "@aws-cdk/aws-elasticloadbalancing"; import { Duration } from "@aws-cdk/core"; import type { GuStack } from "../core"; +import { GuArnParameter } from "../core"; enum RemoveableProperties { SCHEME = "Scheme", @@ -48,3 +54,34 @@ export class GuClassicLoadBalancer extends LoadBalancer { Object.entries(mergedProps.propertiesToOverride).forEach(([key, value]) => cfnLb.addPropertyOverride(key, value)); } } + +interface GuHttpsClassicLoadBalancerProps extends Omit { + listener?: Partial; +} + +export class GuHttpsClassicLoadBalancer extends GuClassicLoadBalancer { + static DefaultListener: LoadBalancerListener = { + internalPort: 9000, + externalPort: 443, + internalProtocol: LoadBalancingProtocol.HTTP, + externalProtocol: LoadBalancingProtocol.HTTPS, + }; + + constructor(scope: GuStack, id: string, props: GuHttpsClassicLoadBalancerProps) { + const listenerProps = { ...GuHttpsClassicLoadBalancer.DefaultListener, ...props.listener }; + + if (!listenerProps.sslCertificateId) { + const certificateId = new GuArnParameter(scope, "CertificateARN", { + description: "Certificate ARN for ELB", + }); + listenerProps.sslCertificateId = certificateId.valueAsString; + } + + const mergedProps = { + ...props, + listeners: [listenerProps], + }; + + super(scope, id, mergedProps); + } +}