From 38f7189605025c856ab32a1e56144b722b8b7ff0 Mon Sep 17 00:00:00 2001 From: akash1810 Date: Mon, 14 Oct 2024 19:02:14 +0100 Subject: [PATCH] feat(riff-raff.yaml): Add `minInstancesInServiceParameters` when applicable --- .changeset/yellow-chefs-laugh.md | 8 ++ src/experimental/patterns/ec2-app.ts | 5 +- .../deployments/cloudformation.ts | 16 +++ src/riff-raff-yaml-file/index.test.ts | 108 +++++++++++++++++- src/riff-raff-yaml-file/index.ts | 14 ++- 5 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 .changeset/yellow-chefs-laugh.md diff --git a/.changeset/yellow-chefs-laugh.md b/.changeset/yellow-chefs-laugh.md new file mode 100644 index 000000000..9f3e6b444 --- /dev/null +++ b/.changeset/yellow-chefs-laugh.md @@ -0,0 +1,8 @@ +--- +"@guardian/cdk": minor +--- + +feat(riff-raff.yaml): Add `minInstancesInServiceParameters` when applicable + +To complement the changes in https://github.com/guardian/riff-raff/pull/1383, +add the `minInstancesInServiceParameters` property to the `riff-raff.yaml` file when applicable. diff --git a/src/experimental/patterns/ec2-app.ts b/src/experimental/patterns/ec2-app.ts index 9f6acc2a2..098fcd27b 100644 --- a/src/experimental/patterns/ec2-app.ts +++ b/src/experimental/patterns/ec2-app.ts @@ -109,9 +109,10 @@ class AutoScalingRollingUpdateTimeout implements IAspect { * * @see https://github.com/guardian/testing-asg-rolling-update */ -class HorizontallyScalingDeploymentProperties implements IAspect { +// eslint-disable-next-line custom-rules/experimental-classes -- this class is not indented for public use +export class HorizontallyScalingDeploymentProperties implements IAspect { public readonly stack: GuStack; - private readonly asgToParamMap: Map; + public readonly asgToParamMap: Map; private static instance: HorizontallyScalingDeploymentProperties | undefined; private constructor(scope: GuStack) { diff --git a/src/riff-raff-yaml-file/deployments/cloudformation.ts b/src/riff-raff-yaml-file/deployments/cloudformation.ts index 7483b5385..054a1e711 100644 --- a/src/riff-raff-yaml-file/deployments/cloudformation.ts +++ b/src/riff-raff-yaml-file/deployments/cloudformation.ts @@ -1,4 +1,5 @@ import type { GuAutoScalingGroup } from "../../constructs/autoscaling"; +import { getAsgRollingUpdateCfnParameterName } from "../../experimental/patterns/ec2-app"; import type { CdkStacksDifferingOnlyByStage, RiffRaffDeployment, RiffRaffDeploymentParameters } from "../types"; export function cloudFormationDeployment( @@ -72,3 +73,18 @@ export function getAmiParameters(autoScalingGroups: GuAutoScalingGroup[]): RiffR }; }, {}); } + +export function getMinInstancesInServiceParameters( + autoScalingGroups: GuAutoScalingGroup[], +): RiffRaffDeploymentParameters { + return autoScalingGroups.reduce((acc, asg) => { + const { app } = asg; + const cfnParameter = getAsgRollingUpdateCfnParameterName(asg); + return { + ...acc, + [cfnParameter]: { + App: app, + }, + }; + }, {}); +} diff --git a/src/riff-raff-yaml-file/index.test.ts b/src/riff-raff-yaml-file/index.test.ts index cee641bdd..f94f81497 100644 --- a/src/riff-raff-yaml-file/index.test.ts +++ b/src/riff-raff-yaml-file/index.test.ts @@ -1,14 +1,16 @@ import { App, Duration } from "aws-cdk-lib"; -import { UpdatePolicy } from "aws-cdk-lib/aws-autoscaling"; +import { CfnScalingPolicy, UpdatePolicy } from "aws-cdk-lib/aws-autoscaling"; import { InstanceClass, InstanceSize, InstanceType } from "aws-cdk-lib/aws-ec2"; import { Schedule } from "aws-cdk-lib/aws-events"; import { Runtime } from "aws-cdk-lib/aws-lambda"; import { AccessScope } from "../constants"; +import type { GuAutoScalingGroup } from "../constructs/autoscaling"; import type { GuStackProps } from "../constructs/core"; import { GuStack } from "../constructs/core"; import { GuLambdaFunction } from "../constructs/lambda"; -import { GuEc2AppExperimental } from "../experimental/patterns/ec2-app"; +import { getAsgRollingUpdateCfnParameterName, GuEc2AppExperimental } from "../experimental/patterns/ec2-app"; import { GuEc2App, GuNodeApp, GuPlayApp, GuScheduledLambda } from "../patterns"; +import { getTemplateAfterAspectInvocation } from "../utils/test"; import { RiffRaffYamlFile } from "./index"; describe("The RiffRaffYamlFile class", () => { @@ -1413,4 +1415,106 @@ describe("The RiffRaffYamlFile class", () => { " `); }); + + it("Should include minInstancesInServiceParameters when GuEc2AppExperimental has a scaling policy", () => { + const app = new App({ outdir: "/tmp/cdk.out" }); + + class MyApplicationStack extends GuStack { + public readonly asg: GuAutoScalingGroup; + + // eslint-disable-next-line custom-rules/valid-constructors -- unit testing + constructor(app: App, id: string, props: GuStackProps) { + super(app, id, props); + + const appName = "my-app"; + + const { autoScalingGroup } = new GuEc2AppExperimental(this, { + app: appName, + instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.MICRO), + access: { scope: AccessScope.PUBLIC }, + userData: { + distributable: { + fileName: `${appName}.deb`, + executionStatement: `dpkg -i /${appName}/${appName}.deb`, + }, + }, + certificateProps: { + domainName: "rip.gu.com", + }, + monitoringConfiguration: { noMonitoring: true }, + scaling: { + minimumInstances: 1, + }, + applicationPort: 9000, + imageRecipe: "arm64-bionic-java11-deploy-infrastructure", + buildIdentifier: "TEST", + }); + + new CfnScalingPolicy(autoScalingGroup, "ScaleOut", { + autoScalingGroupName: autoScalingGroup.autoScalingGroupName, + policyType: "SimpleScaling", + adjustmentType: "ChangeInCapacity", + scalingAdjustment: 1, + }); + + this.asg = autoScalingGroup; + } + } + + const guStack = new MyApplicationStack(app, "test-stack", { + stack: "test", + stage: "TEST", + env: { region: "eu-west-1" }, + }); + + // Ensure the Aspects are invoked... + getTemplateAfterAspectInvocation(guStack); + + // ...so that the CFN Parameters are added to the template, to then be processed by the `RiffRaffYamlFile` + const actual = new RiffRaffYamlFile(app).toYAML(); + + const cfnParameterName = getAsgRollingUpdateCfnParameterName(guStack.asg); + + expect(actual).toMatchInlineSnapshot(` + "allowedStages: + - TEST + deployments: + asg-upload-eu-west-1-test-my-app: + type: autoscaling + actions: + - uploadArtifacts + regions: + - eu-west-1 + stacks: + - test + app: my-app + parameters: + bucketSsmLookup: true + prefixApp: true + contentDirectory: my-app + cfn-eu-west-1-test-my-application-stack: + type: cloud-formation + regions: + - eu-west-1 + stacks: + - test + app: my-application-stack + contentDirectory: /tmp/cdk.out + parameters: + templateStagePaths: + TEST: test-stack.template.json + amiParametersToTags: + AMIMyapp: + BuiltBy: amigo + AmigoStage: PROD + Recipe: arm64-bionic-java11-deploy-infrastructure + Encrypted: 'true' + minInstancesInServiceParameters: + ${cfnParameterName}: + App: my-app + dependencies: + - asg-upload-eu-west-1-test-my-app + " + `); + }); }); diff --git a/src/riff-raff-yaml-file/index.ts b/src/riff-raff-yaml-file/index.ts index 2c5eba421..56a5e8177 100644 --- a/src/riff-raff-yaml-file/index.ts +++ b/src/riff-raff-yaml-file/index.ts @@ -7,8 +7,13 @@ import { dump } from "js-yaml"; import { GuAutoScalingGroup } from "../constructs/autoscaling"; import { GuStack } from "../constructs/core"; import { GuLambdaFunction } from "../constructs/lambda"; +import { HorizontallyScalingDeploymentProperties } from "../experimental/patterns/ec2-app"; import { autoscalingDeployment, uploadAutoscalingArtifact } from "./deployments/autoscaling"; -import { cloudFormationDeployment, getAmiParameters } from "./deployments/cloudformation"; +import { + cloudFormationDeployment, + getAmiParameters, + getMinInstancesInServiceParameters, +} from "./deployments/cloudformation"; import { updateLambdaDeployment, uploadLambdaArtifact } from "./deployments/lambda"; import { groupByClassNameStackRegionStage } from "./group-by"; import type { @@ -271,6 +276,10 @@ export class RiffRaffYamlFile { const amiParametersToTags = getAmiParameters(autoscalingGroups); + const minInServiceParamMap = HorizontallyScalingDeploymentProperties.getInstance(stack).asgToParamMap; + const minInServiceAsgs = autoscalingGroups.filter((asg) => minInServiceParamMap.has(asg.node.id)); + const minInstancesInServiceParameters = getMinInstancesInServiceParameters(minInServiceAsgs); + deployments.set(cfnDeployment.name, { ...cfnDeployment.props, parameters: { @@ -278,6 +287,9 @@ export class RiffRaffYamlFile { // only add the `amiParametersToTags` property if there are some ...(autoscalingGroups.length > 0 && { amiParametersToTags }), + + // only add the `minInstancesInServiceParameters` property if there are some + ...(minInServiceAsgs.length > 0 && { minInstancesInServiceParameters }), }, }); });