From 1eab0c0a33fbb463ec0c95517278444d69544249 Mon Sep 17 00:00:00 2001 From: akash1810 Date: Sat, 14 Sep 2024 11:13:49 +0100 Subject: [PATCH] feat: Use `GuEc2AppExperimental` to deploy app updates via CFN See https://github.com/guardian/cdk/pull/2417. --- .github/workflows/ci.yml | 8 +- cdk/jest.setup.js | 2 + .../__snapshots__/amiable.test.ts.snap | 190 ++++++++++++++++-- cdk/lib/amiable/amiable.ts | 9 +- cdk/package-lock.json | 5 +- cdk/package.json | 12 +- script/ci | 4 +- 7 files changed, 201 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6a00b96..799bd4d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,10 @@ jobs: distribution: 'corretto' cache: 'sbt' + - run: | + LAST_TEAMCITY_BUILD=1007 + echo "BUILD_NUMBER=$(( $GITHUB_RUN_NUMBER + $LAST_TEAMCITY_BUILD ))" >> $GITHUB_ENV + # See https://github.com/github/scripts-to-rule-them-all - name: Run script/ci run: ./script/ci @@ -50,11 +54,11 @@ jobs: with: githubToken: ${{ secrets.GITHUB_TOKEN }} roleArn: ${{ secrets.GU_RIFF_RAFF_ROLE_ARN }} - buildNumberOffset: 1007 + buildNumber: ${{ env.BUILD_NUMBER }} projectName: tools::amiable configPath: cdk/cdk.out/riff-raff.yaml contentDirectories: | cdk.out: - cdk/cdk.out amiable: - - amiable.deb + - dist diff --git a/cdk/jest.setup.js b/cdk/jest.setup.js index 993e2e71..38d94219 100644 --- a/cdk/jest.setup.js +++ b/cdk/jest.setup.js @@ -1 +1,3 @@ jest.mock("@guardian/cdk/lib/constants/tracking-tag"); + +process.env.BUILD_NUMBER = "TEST"; diff --git a/cdk/lib/amiable/__snapshots__/amiable.test.ts.snap b/cdk/lib/amiable/__snapshots__/amiable.test.ts.snap index ae878a5a..50ce0555 100644 --- a/cdk/lib/amiable/__snapshots__/amiable.test.ts.snap +++ b/cdk/lib/amiable/__snapshots__/amiable.test.ts.snap @@ -10,7 +10,7 @@ exports[`The Amiable stack matches the snapshot 1`] = ` "GuVpcParameter", "GuSubnetListParameter", "GuSubnetListParameter", - "GuPlayApp", + "GuEc2AppExperimental", "GuCertificate", "GuInstanceRole", "GuSsmSshPolicy", @@ -104,8 +104,46 @@ exports[`The Amiable stack matches the snapshot 1`] = ` }, "Type": "AWS::SSM::Parameter", }, + "AsgRollingUpdatePolicy2A1DDC6F": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "cloudformation:SignalResource", + "Effect": "Allow", + "Resource": { + "Ref": "AWS::StackId", + }, + }, + { + "Action": "elasticloadbalancing:DescribeTargetHealth", + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AsgRollingUpdatePolicy2A1DDC6F", + "Roles": [ + { + "Ref": "InstanceRoleAmiable56110022", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, "AutoScalingGroupAmiableASGFCD99427": { + "CreationPolicy": { + "AutoScalingCreationPolicy": { + "MinSuccessfulInstancesPercent": 100, + }, + "ResourceSignal": { + "Count": 1, + "Timeout": "PT5M", + }, + }, "Properties": { + "DesiredCapacity": "1", "HealthCheckGracePeriod": 120, "HealthCheckType": "ELB", "LaunchTemplate": { @@ -123,10 +161,6 @@ exports[`The Amiable stack matches the snapshot 1`] = ` "MetricsCollection": [ { "Granularity": "1Minute", - "Metrics": [ - "GroupTotalInstances", - "GroupInServiceInstances", - ], }, ], "MinSize": "1", @@ -179,6 +213,18 @@ exports[`The Amiable stack matches the snapshot 1`] = ` }, }, "Type": "AWS::AutoScaling::AutoScalingGroup", + "UpdatePolicy": { + "AutoScalingRollingUpdate": { + "MaxBatchSize": 2, + "MinInstancesInService": 1, + "MinSuccessfulInstancesPercent": 100, + "PauseTime": "PT5M", + "SuspendProcesses": [ + "AlarmNotification", + ], + "WaitOnResourceSignals": true, + }, + }, }, "CertificateAmiable3AC0F81D": { "DeletionPolicy": "Retain", @@ -1037,6 +1083,17 @@ exports[`The Amiable stack matches the snapshot 1`] = ` "", [ "#!/bin/bash +function exitTrap(){ +exitCode=$? + + cfn-signal --stack ", + { + "Ref": "AWS::StackId", + }, + " --resource AutoScalingGroupAmiableASGFCD99427 --region eu-west-1 --exit-code $exitCode || echo 'Failed to send Cloudformation Signal' + +} +trap exitTrap EXIT mkdir /amiable aws --region eu-west-1 s3 cp s3://", @@ -1053,9 +1110,33 @@ exports[`The Amiable stack matches the snapshot 1`] = ` { "Ref": "DistributionBucketName", }, - "/deploy/CODE/amiable/amiable.deb /amiable/ + "/deploy/CODE/amiable/amiable-TEST.deb /amiable/amiable.deb + + dpkg -i /amiable/amiable.deb +# GuEc2AppExperimental UserData Start + + TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") + INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" "http://169.254.169.254/latest/meta-data/instance-id") + + STATE=$(aws elbv2 describe-target-health --target-group-arn ", + { + "Ref": "TargetGroupAmiable6987B5AC", + }, + " --region eu-west-1 --targets Id=$INSTANCE_ID,Port=9000 --query "TargetHealthDescriptions[0].TargetHealth.State") + + until [ "$STATE" == "\\"healthy\\"" ]; do + echo "Instance not yet healthy within target group. Current state $STATE. Sleeping..." + sleep 5 + STATE=$(aws elbv2 describe-target-health --target-group-arn ", + { + "Ref": "TargetGroupAmiable6987B5AC", + }, + " --region eu-west-1 --targets Id=$INSTANCE_ID,Port=9000 --query "TargetHealthDescriptions[0].TargetHealth.State") + done - dpkg -i /amiable/amiable.deb", + echo "Instance is healthy in target group." + +# GuEc2AppExperimental UserData End", ], ], }, @@ -1201,7 +1282,7 @@ exports[`The Amiable stack matches the snapshot 2`] = ` "GuVpcParameter", "GuSubnetListParameter", "GuSubnetListParameter", - "GuPlayApp", + "GuEc2AppExperimental", "GuCertificate", "GuInstanceRole", "GuSsmSshPolicy", @@ -1297,8 +1378,46 @@ exports[`The Amiable stack matches the snapshot 2`] = ` }, "Type": "AWS::SSM::Parameter", }, + "AsgRollingUpdatePolicy2A1DDC6F": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "cloudformation:SignalResource", + "Effect": "Allow", + "Resource": { + "Ref": "AWS::StackId", + }, + }, + { + "Action": "elasticloadbalancing:DescribeTargetHealth", + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AsgRollingUpdatePolicy2A1DDC6F", + "Roles": [ + { + "Ref": "InstanceRoleAmiable56110022", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, "AutoScalingGroupAmiableASGFCD99427": { + "CreationPolicy": { + "AutoScalingCreationPolicy": { + "MinSuccessfulInstancesPercent": 100, + }, + "ResourceSignal": { + "Count": 1, + "Timeout": "PT5M", + }, + }, "Properties": { + "DesiredCapacity": "1", "HealthCheckGracePeriod": 120, "HealthCheckType": "ELB", "LaunchTemplate": { @@ -1316,10 +1435,6 @@ exports[`The Amiable stack matches the snapshot 2`] = ` "MetricsCollection": [ { "Granularity": "1Minute", - "Metrics": [ - "GroupTotalInstances", - "GroupInServiceInstances", - ], }, ], "MinSize": "1", @@ -1372,6 +1487,18 @@ exports[`The Amiable stack matches the snapshot 2`] = ` }, }, "Type": "AWS::AutoScaling::AutoScalingGroup", + "UpdatePolicy": { + "AutoScalingRollingUpdate": { + "MaxBatchSize": 2, + "MinInstancesInService": 1, + "MinSuccessfulInstancesPercent": 100, + "PauseTime": "PT5M", + "SuspendProcesses": [ + "AlarmNotification", + ], + "WaitOnResourceSignals": true, + }, + }, }, "CertificateAmiable3AC0F81D": { "DeletionPolicy": "Retain", @@ -2465,6 +2592,17 @@ exports[`The Amiable stack matches the snapshot 2`] = ` "", [ "#!/bin/bash +function exitTrap(){ +exitCode=$? + + cfn-signal --stack ", + { + "Ref": "AWS::StackId", + }, + " --resource AutoScalingGroupAmiableASGFCD99427 --region eu-west-1 --exit-code $exitCode || echo 'Failed to send Cloudformation Signal' + +} +trap exitTrap EXIT mkdir /amiable aws --region eu-west-1 s3 cp s3://", @@ -2481,9 +2619,33 @@ exports[`The Amiable stack matches the snapshot 2`] = ` { "Ref": "DistributionBucketName", }, - "/deploy/PROD/amiable/amiable.deb /amiable/ + "/deploy/PROD/amiable/amiable-TEST.deb /amiable/amiable.deb + + dpkg -i /amiable/amiable.deb +# GuEc2AppExperimental UserData Start + + TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") + INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" "http://169.254.169.254/latest/meta-data/instance-id") + + STATE=$(aws elbv2 describe-target-health --target-group-arn ", + { + "Ref": "TargetGroupAmiable6987B5AC", + }, + " --region eu-west-1 --targets Id=$INSTANCE_ID,Port=9000 --query "TargetHealthDescriptions[0].TargetHealth.State") + + until [ "$STATE" == "\\"healthy\\"" ]; do + echo "Instance not yet healthy within target group. Current state $STATE. Sleeping..." + sleep 5 + STATE=$(aws elbv2 describe-target-health --target-group-arn ", + { + "Ref": "TargetGroupAmiable6987B5AC", + }, + " --region eu-west-1 --targets Id=$INSTANCE_ID,Port=9000 --query "TargetHealthDescriptions[0].TargetHealth.State") + done - dpkg -i /amiable/amiable.deb", + echo "Instance is healthy in target group." + +# GuEc2AppExperimental UserData End", ], ], }, diff --git a/cdk/lib/amiable/amiable.ts b/cdk/lib/amiable/amiable.ts index e81c541a..41e33059 100644 --- a/cdk/lib/amiable/amiable.ts +++ b/cdk/lib/amiable/amiable.ts @@ -4,7 +4,7 @@ import { GuDistributionBucketParameter, GuStack, GuStringParameter } from "@guar import { GuCname } from "@guardian/cdk/lib/constructs/dns"; import { GuHttpsEgressSecurityGroup } from "@guardian/cdk/lib/constructs/ec2"; import { GuAllowPolicy, GuSESSenderPolicy } from "@guardian/cdk/lib/constructs/iam"; -import { GuPlayApp } from "@guardian/cdk/lib/patterns/ec2-app"; +import { GuEc2AppExperimental } from "@guardian/cdk/lib/experimental/patterns/ec2-app"; import type { App } from "aws-cdk-lib"; import { Duration, SecretValue } from "aws-cdk-lib"; import { InstanceClass, InstanceSize, InstanceType, UserData } from "aws-cdk-lib/aws-ec2"; @@ -27,16 +27,19 @@ export class Amiable extends GuStack { const distBucket = GuDistributionBucketParameter.getInstance(this).valueAsString; + const buildNumber = process.env.BUILD_NUMBER ?? "DEV"; + const userData = UserData.forLinux(); userData.addCommands(` mkdir /amiable aws --region eu-west-1 s3 cp s3://${distBucket}/${stack}/${stage}/${app}/conf/amiable-service-account-cert.json /amiable/ aws --region eu-west-1 s3 cp s3://${distBucket}/${stack}/${stage}/${app}/conf/amiable.conf /etc/ - aws --region eu-west-1 s3 cp s3://${distBucket}/${stack}/${stage}/${app}/amiable.deb /amiable/ + aws --region eu-west-1 s3 cp s3://${distBucket}/${stack}/${stage}/${app}/amiable-${buildNumber}.deb /amiable/amiable.deb dpkg -i /amiable/amiable.deb`); - const ec2App = new GuPlayApp(this, { + const ec2App = new GuEc2AppExperimental(this, { + applicationPort: 9000, app, instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.SMALL), userData, diff --git a/cdk/package-lock.json b/cdk/package-lock.json index 41206d74..29ab04bb 100644 --- a/cdk/package-lock.json +++ b/cdk/package-lock.json @@ -15,7 +15,7 @@ "cdk": "node_modules/.bin/cdk" }, "devDependencies": { - "@guardian/cdk": "59.3.5", + "@guardian/cdk": "github:guardian/cdk#aa/ec2-AutoScalingReplacingUpdate", "@guardian/eslint-config-typescript": "^12.0.0", "@types/jest": "^29.5.11", "@types/node": "22.5.1", @@ -881,8 +881,7 @@ }, "node_modules/@guardian/cdk": { "version": "59.3.5", - "resolved": "https://registry.npmjs.org/@guardian/cdk/-/cdk-59.3.5.tgz", - "integrity": "sha512-2RJyR+cLvsaBVfqOsPms65lGgxotz3JchX3gNwps1iBvr2sXTFBi8hWQJiTtgzsORpyuRettrS+m4v9R7VCiRA==", + "resolved": "git+ssh://git@github.com/guardian/cdk.git#b4801a052a6f662c20db124d097588a123333d40", "dev": true, "dependencies": { "@oclif/core": "3.26.6", diff --git a/cdk/package.json b/cdk/package.json index 3d3d4690..071f4fd3 100644 --- a/cdk/package.json +++ b/cdk/package.json @@ -17,19 +17,19 @@ "synth": "cdk synth --path-metadata false --version-reporting false" }, "devDependencies": { + "@guardian/cdk": "github:guardian/cdk#aa/ec2-AutoScalingReplacingUpdate", "@guardian/eslint-config-typescript": "^12.0.0", "@types/jest": "^29.5.11", - "eslint": "^8.57.0", "@types/node": "22.5.1", + "aws-cdk": "2.157.0", + "aws-cdk-lib": "2.157.0", + "constructs": "10.3.0", + "eslint": "^8.57.0", "jest": "^29.7.0", "prettier": "^3.3.3", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", - "typescript": "~5.5.4", - "@guardian/cdk": "59.3.5", - "aws-cdk": "2.157.0", - "aws-cdk-lib": "2.157.0", - "constructs": "10.3.0" + "typescript": "~5.5.4" }, "dependencies": { "source-map-support": "^0.5.16" diff --git a/script/ci b/script/ci index a58a9d79..7593797a 100755 --- a/script/ci +++ b/script/ci @@ -9,4 +9,6 @@ set -e sbt clean scalafmtCheckAll compile test debian:packageBin -mv target/*.deb ./amiable.deb + +mkdir -p dist +mv target/*.deb "dist/amiable-${BUILD_NUMBER}.deb"