diff --git a/.changeset/stupid-apes-poke.md b/.changeset/stupid-apes-poke.md new file mode 100644 index 0000000000..6f7a4e148b --- /dev/null +++ b/.changeset/stupid-apes-poke.md @@ -0,0 +1,11 @@ +--- +"@guardian/cdk": major +--- + +Remove support for classic load balancers. + +Use of application load balancers (ALBs) is considered best practice, +as ALBs are receiving [more capabilities](https://aws.amazon.com/elasticloadbalancing/features/) than elastic (classic) load balancers (ELBs). +GuCDK should be encoding best practice, so remove support for ELBs. + +Please adopt application load balancers instead, or if necessary, use ELBs directly from AWS CDK. diff --git a/src/constructs/loadbalancing/elb.test.ts b/src/constructs/loadbalancing/elb.test.ts deleted file mode 100644 index 780d5d41b8..0000000000 --- a/src/constructs/loadbalancing/elb.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { Stack } from "aws-cdk-lib"; -import { Match, Template } from "aws-cdk-lib/assertions"; -import { Vpc } from "aws-cdk-lib/aws-ec2"; -import { GuTemplate, simpleGuStackForTesting } from "../../utils/test"; -import type { AppIdentity } from "../core"; -import { GuClassicLoadBalancer, GuHttpsClassicLoadBalancer } from "./elb"; - -describe("The GuClassicLoadBalancer class", () => { - const vpc = Vpc.fromVpcAttributes(new Stack(), "VPC", { - vpcId: "test", - availabilityZones: [""], - publicSubnetIds: [""], - privateSubnetIds: [""], - }); - - const app: AppIdentity = { app: "testing" }; - - test("applies the App tag", () => { - const stack = simpleGuStackForTesting(); - new GuClassicLoadBalancer(stack, "ClassicLoadBalancer", { ...app, vpc }); - - GuTemplate.fromStack(stack).hasGuTaggedResource("AWS::ElasticLoadBalancing::LoadBalancer", { - appIdentity: { app: "testing" }, - }); - }); - - test("overrides any properties as required", () => { - const stack = simpleGuStackForTesting(); - new GuClassicLoadBalancer(stack, "ClassicLoadBalancer", { - ...app, - vpc, - propertiesToOverride: { - AccessLoggingPolicy: { - EmitInterval: 5, - Enabled: true, - }, - }, - }); - - Template.fromStack(stack).hasResourceProperties("AWS::ElasticLoadBalancing::LoadBalancer", { - AccessLoggingPolicy: { - EmitInterval: 5, - Enabled: true, - }, - }); - }); - - test("uses default health check properties", () => { - const stack = simpleGuStackForTesting(); - new GuClassicLoadBalancer(stack, "ClassicLoadBalancer", { - ...app, - vpc, - }); - - Template.fromStack(stack).hasResourceProperties("AWS::ElasticLoadBalancing::LoadBalancer", { - HealthCheck: { - HealthyThreshold: "2", - Interval: "30", - Target: "HTTP:9000/healthcheck", - Timeout: "10", - UnhealthyThreshold: "5", - }, - }); - }); - - test("merges any health check properties provided", () => { - const stack = simpleGuStackForTesting(); - new GuClassicLoadBalancer(stack, "ClassicLoadBalancer", { - ...app, - vpc, - healthCheck: { - path: "/test", - }, - }); - - Template.fromStack(stack).hasResourceProperties("AWS::ElasticLoadBalancing::LoadBalancer", { - HealthCheck: { - HealthyThreshold: "2", - Interval: "30", - Target: "HTTP:9000/test", - Timeout: "10", - UnhealthyThreshold: "5", - }, - }); - }); -}); - -describe("The GuHttpsClassicLoadBalancer class", () => { - const vpc = Vpc.fromVpcAttributes(new Stack(), "VPC", { - vpcId: "test", - availabilityZones: [""], - publicSubnetIds: [""], - privateSubnetIds: [""], - }); - - const app: AppIdentity = { app: "testing" }; - - test("uses default listener values", () => { - const stack = simpleGuStackForTesting(); - new GuHttpsClassicLoadBalancer(stack, "HttpsClassicLoadBalancer", { - ...app, - vpc, - }); - - Template.fromStack(stack).hasResourceProperties("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", { - ...app, - vpc, - }); - - Template.fromStack(stack).hasParameter("CertificateARN", { - 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", { - ...app, - vpc, - listener: { - sslCertificateArn: "certificateId", - }, - }); - - const template = Template.fromStack(stack); - - template.hasResourceProperties("AWS::ElasticLoadBalancing::LoadBalancer", { - Listeners: [ - { - InstancePort: "9000", - InstanceProtocol: "http", - LoadBalancerPort: "443", - Protocol: "https", - SSLCertificateId: "certificateId", - }, - ], - }); - - expect(template.findParameters("*")).toMatchObject({}); - }); - - test("merges any listener values provided", () => { - const stack = simpleGuStackForTesting(); - new GuHttpsClassicLoadBalancer(stack, "HttpsClassicLoadBalancer", { - ...app, - vpc, - listener: { - internalPort: 3000, - }, - }); - - Template.fromStack(stack).hasResourceProperties("AWS::ElasticLoadBalancing::LoadBalancer", { - Listeners: [ - { - InstancePort: "3000", - InstanceProtocol: "http", - LoadBalancerPort: "443", - Protocol: "https", - SSLCertificateId: { - Ref: "CertificateARN", - }, - }, - ], - }); - }); - - test("Removes Scheme if user asks us to", () => { - const stack = simpleGuStackForTesting(); - new GuClassicLoadBalancer(stack, "ClassicLoadBalancer", { - ...app, - vpc, - removeScheme: true, - }); - - Template.fromStack(stack).hasResourceProperties("AWS::ElasticLoadBalancing::LoadBalancer", { - Scheme: Match.absent(), - }); - }); -}); diff --git a/src/constructs/loadbalancing/elb.ts b/src/constructs/loadbalancing/elb.ts deleted file mode 100644 index 35c96597e2..0000000000 --- a/src/constructs/loadbalancing/elb.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { Duration } from "aws-cdk-lib"; -import { LoadBalancer, LoadBalancingProtocol } from "aws-cdk-lib/aws-elasticloadbalancing"; -import type { - CfnLoadBalancer, - HealthCheck, - LoadBalancerListener, - LoadBalancerProps, -} from "aws-cdk-lib/aws-elasticloadbalancing"; -import { GuAppAwareConstruct } from "../../utils/mixin/app-aware-construct"; -import { GuArnParameter } from "../core"; -import type { AppIdentity, GuStack } from "../core"; - -interface GuClassicLoadBalancerProps extends Omit, AppIdentity { - propertiesToOverride?: Record; - healthCheck?: Partial; - /** - * If your CloudFormation does not define the Scheme of your Load Balancer, you must set this boolean to true to avoid - * resource [replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-elb.html#cfn-ec2-elb-scheme). - * If a Load Balancer is replaced it is likely to lead to downtime. - */ - removeScheme?: boolean; -} - -/** - * **IMPORTANT**: This construct should **only** be used if you are migrating an existing stack and you need to retain the load balancer. - * Please use [[`GuApplicationLoadBalancer`]] instead of [[`GuClassicLoadBalancer`]] wherever possible. - * - * By default, load balancers created via this construct will perform a healthcheck against `/healthcheck` on port 9000. All healthcheck - * defaults can be overridden via the `healthcheck` prop. - * - * For example, to use `/test` for the healthcheck path use: - * - * ```typescript - * new GuClassicLoadBalancer(stack, "ClassicLoadBalancer", { - * // other props - * healthCheck: { - * path: "/test", - * }, - * }); - * ``` - * If you are running an application which only accepts traffic over HTTPs, consider using [[`GuHttpsClassicLoadBalancer`]] - * to reduce the amount of boilerplate needed when configuring your load balancer. - * - * This resource is stateful. - * @see https://github.com/guardian/cdk/blob/main/docs/stateful-resources.md - */ -export class GuClassicLoadBalancer extends GuAppAwareConstruct(LoadBalancer) { - static DefaultHealthCheck = { - port: 9000, - path: "/healthcheck", - protocol: LoadBalancingProtocol.HTTP, - healthyThreshold: 2, - unhealthyThreshold: 5, - interval: Duration.seconds(30), - timeout: Duration.seconds(10), - }; - - constructor(scope: GuStack, id: string, props: GuClassicLoadBalancerProps) { - const mergedProps = { - ...props, - healthCheck: { ...GuClassicLoadBalancer.DefaultHealthCheck, ...props.healthCheck }, - }; - - super(scope, id, mergedProps); - - const cfnLb = this.node.defaultChild as CfnLoadBalancer; - - if (props.removeScheme) { - cfnLb.addPropertyDeletionOverride("Scheme"); - } - - mergedProps.propertiesToOverride && - Object.entries(mergedProps.propertiesToOverride).forEach(([key, value]) => cfnLb.addPropertyOverride(key, value)); - } -} - -interface GuHttpsClassicLoadBalancerProps extends Omit { - listener?: Partial; -} - -/** - * **IMPORTANT**: This construct should **only** be used if you are migrating an existing stack and you need to retain the load balancer. - * Please use [[`GuHttpsApplicationListener`]] instead of [[`GuHttpsClassicLoadBalancer`]] wherever possible. - * - * This construct creates a classic load balancer which accepts HTTPS traffic. It communicates with EC2 instances over port 9000 - * by default. This default can be overridden via the listener prop, for example: - * ```typescript - * new GuHttpsClassicLoadBalancer(stack, "HttpsClassicLoadBalancer", { - * // other props - * listener: { - * internalPort: 3000, - * }, - * }); - * ``` - * - * You can pass a certificate id to this construct via the listener interface, for example: - * ```typescript - * new GuHttpsClassicLoadBalancer(stack, "HttpsClassicLoadBalancer", { - * // other props - * listener: { - * sslCertificateId: "certificateId", - * }, - * }); - * ``` - * If certificate id is omitted the library will create a Parameter which allows you to pass in a certificate ARN. - * - * For more details on migrating an existing load balancer and general load balancer defaults, see [[`GuClassicLoadBalancer`]]. - */ -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: LoadBalancerListener = { ...GuHttpsClassicLoadBalancer.DefaultListener, ...props.listener }; - - const mergedProps: GuClassicLoadBalancerProps = { - ...props, - listeners: [ - { - ...listenerProps, - sslCertificateArn: - listenerProps.sslCertificateArn ?? - new GuArnParameter(scope, "CertificateARN", { - description: "Certificate ARN for ELB", - }).valueAsString, - }, - ], - }; - - super(scope, id, mergedProps); - } -} diff --git a/src/constructs/loadbalancing/index.ts b/src/constructs/loadbalancing/index.ts index 9af8480ea1..f0c9c2c710 100644 --- a/src/constructs/loadbalancing/index.ts +++ b/src/constructs/loadbalancing/index.ts @@ -1,2 +1 @@ export * from "./alb"; -export * from "./elb";