diff --git a/dotcom-rendering/cdk/bin/cdk.ts b/dotcom-rendering/cdk/bin/cdk.ts index e50e87d0409..f032211829f 100644 --- a/dotcom-rendering/cdk/bin/cdk.ts +++ b/dotcom-rendering/cdk/bin/cdk.ts @@ -3,6 +3,14 @@ import { InstanceClass, InstanceSize, InstanceType } from 'aws-cdk-lib/aws-ec2'; import type { RenderingCDKStackProps } from '../lib/renderingStack'; import { RenderingCDKStack } from '../lib/renderingStack'; +function getImageIdentifier(): string { + const imageIdentifier: string | undefined = process.env.IMAGE_DIGEST; + if (!imageIdentifier) { + throw new Error('Image identifier must be provided'); + } + return imageIdentifier; +} + const cdkApp = new App(); const cpuScalingSteps = { @@ -97,14 +105,6 @@ new RenderingCDKStack(cdkApp, 'FaciaRendering-PROD', { }); /** Tag pages */ -function getImageIdentifier(): string { - const imageIdentifier: string | undefined = process.env.IMAGE_DIGEST; - if (!imageIdentifier) { - throw new Error('Image identifier must be provided'); - } - return imageIdentifier; -} - export const TagPageRenderingPropsCODE: RenderingCDKStackProps = { guApp: 'tag-page-rendering', stage: 'CODE', @@ -199,15 +199,43 @@ new RenderingCDKStack( TagPageRenderingPropsPROD, ); -/** Interactive */ -new RenderingCDKStack(cdkApp, 'InteractiveRendering-CODE', { +export const InteractiveRenderingPropsCODE: RenderingCDKStackProps = { guApp: 'interactive-rendering', stage: 'CODE', domainName: 'interactive-rendering.code.dev-guardianapis.com', scaling: { minimumInstances: 1, maximumInstances: 3 }, instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.MICRO), -}); -new RenderingCDKStack(cdkApp, 'InteractiveRendering-PROD', { + ecsProps: { + imageIdentifier: getImageIdentifier(), + taskCpu: 1024, + taskMemoryLimitMiB: 2048, + scaling: { + minimumTasks: 1, + maximumTasks: 3, + cpuScaling: { + targetValue: 20, + // TODO: Tune cooldown values. + // https://docs.aws.amazon.com/autoscaling/application/userguide/target-tracking-scaling-policy-overview.html#target-tracking-cooldown + scaleInCooldown: Duration.seconds(60), + scaleOutCooldown: Duration.seconds(60), + }, + }, + + // Route 100% traffic to EC2. + targetGroupWeights: { + ec2: 1, + ecs: 0, + }, + }, +}; +/** Interactive */ +new RenderingCDKStack( + cdkApp, + 'InteractiveRendering-CODE', + InteractiveRenderingPropsCODE, +); + +export const InteractiveRenderingPropsPROD: RenderingCDKStackProps = { guApp: 'interactive-rendering', stage: 'PROD', domainName: 'interactive-rendering.guardianapis.com', @@ -237,4 +265,9 @@ new RenderingCDKStack(cdkApp, 'InteractiveRendering-PROD', { }, }, instanceType: InstanceType.of(InstanceClass.C8G, InstanceSize.MEDIUM), -}); +}; +new RenderingCDKStack( + cdkApp, + 'InteractiveRendering-PROD', + InteractiveRenderingPropsPROD, +); diff --git a/dotcom-rendering/cdk/lib/__snapshots__/renderingStack.test.ts.snap b/dotcom-rendering/cdk/lib/__snapshots__/renderingStack.test.ts.snap index a2e5c7f92be..1bf28104904 100644 --- a/dotcom-rendering/cdk/lib/__snapshots__/renderingStack.test.ts.snap +++ b/dotcom-rendering/cdk/lib/__snapshots__/renderingStack.test.ts.snap @@ -1666,6 +1666,3750 @@ systemctl start article-rendering", } `; +exports[`The RenderingCDKStack matches the snapshot for Interactive Rendering CODE (uses ECS) 1`] = ` +{ + "Metadata": { + "gu:cdk:constructs": [ + "GuDistributionBucketParameter", + "GuAllowPolicy", + "GuAllowPolicy", + "GuAllowPolicy", + "GuVpcParameter", + "GuSubnetListParameter", + "GuSubnetListParameter", + "GuLoadBalancedAppExperimental", + "GuInstanceRole", + "GuSsmSshPolicy", + "GuDescribeEC2Policy", + "GuLoggingStreamNameParameter", + "GuLogShippingPolicy", + "GuGetDistributablePolicy", + "GuParameterStoreReadPolicy", + "GuAmiParameter", + "GuHttpsEgressSecurityGroup", + "GuAutoScalingGroup", + "GuApplicationTargetGroup", + "GuRiffRaffDeploymentIdParameterExperimental", + "GuHttpsEgressSecurityGroup", + "GuApplicationTargetGroup", + "GuCertificate", + "GuApplicationLoadBalancer", + "GuAccessLoggingBucketParameter", + "GuHttpsApplicationListener", + "GuSecurityGroup", + "GuCname", + ], + "gu:cdk:version": "TEST", + }, + "Outputs": { + "LoadBalancerInteractiverenderingDnsName": { + "Description": "DNS entry for LoadBalancerInteractiverendering", + "Value": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingB53453B9", + "DNSName", + ], + }, + }, + }, + "Parameters": { + "AMIInteractiverendering": { + "Description": "Amazon Machine Image ID for the app interactive-rendering. Use this in conjunction with AMIgo to keep AMIs up to date.", + "Type": "AWS::EC2::Image::Id", + }, + "AccessLoggingBucket": { + "Default": "/account/services/access-logging/bucket", + "Description": "S3 bucket to store your access logs", + "Type": "AWS::SSM::Parameter::Value", + }, + "ArtifactAccountIdParameter": { + "Default": "/organisation/accounts/artifacts", + "Type": "AWS::SSM::Parameter::Value", + }, + "DistributionBucketName": { + "Default": "/account/services/artifact.bucket", + "Description": "SSM parameter containing the S3 bucket name holding distribution artifacts", + "Type": "AWS::SSM::Parameter::Value", + }, + "LoggingStreamName": { + "Default": "/account/services/logging.stream.name", + "Description": "SSM parameter containing the Name (not ARN) on the kinesis stream", + "Type": "AWS::SSM::Parameter::Value", + }, + "RiffRaffDeploymentId": { + "Description": "Used by Riff-Raff to inject the deployment ID.", + "Type": "String", + }, + "VpcId": { + "Default": "/account/vpc/primary/id", + "Description": "Virtual Private Cloud to run EC2 instances within. Should NOT be the account default VPC.", + "Type": "AWS::SSM::Parameter::Value", + }, + "interactiverenderingPrivateSubnets": { + "Default": "/account/vpc/primary/subnets/private", + "Description": "A list of private subnets", + "Type": "AWS::SSM::Parameter::Value>", + }, + "interactiverenderingPublicSubnets": { + "Default": "/account/vpc/primary/subnets/public", + "Description": "A list of public subnets", + "Type": "AWS::SSM::Parameter::Value>", + }, + }, + "Resources": { + "AllowPolicyCloudwatchLogsA783E5B4": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cloudwatch:*", + "logs:*", + ], + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AllowPolicyCloudwatchLogsA783E5B4", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + { + "Ref": "EcsTaskDefinitionTaskRoleB7B6D8DD", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "AllowPolicyDescribeDecryptKmsE91286F3": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:kms:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":FrontendConfigKey", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AllowPolicyDescribeDecryptKmsE91286F3", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + { + "Ref": "EcsTaskDefinitionTaskRoleB7B6D8DD", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "AllowPolicyGetSsmParamsByPathB54B2DE8": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ssm:GetParametersByPath", + "ssm:GetParameter", + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":parameter/frontend/*", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":parameter/dotcom/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AllowPolicyGetSsmParamsByPathB54B2DE8", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + { + "Ref": "EcsTaskDefinitionTaskRoleB7B6D8DD", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "AutoScalingGroupInteractiverenderingASGCDA22918": { + "Properties": { + "HealthCheckGracePeriod": 120, + "HealthCheckType": "ELB", + "LaunchTemplate": { + "LaunchTemplateId": { + "Ref": "frontendCODEinteractiverenderingD1F3B137", + }, + "Version": { + "Fn::GetAtt": [ + "frontendCODEinteractiverenderingD1F3B137", + "LatestVersionNumber", + ], + }, + }, + "MaxSize": "3", + "MetricsCollection": [ + { + "Granularity": "1Minute", + }, + ], + "MinSize": "1", + "Tags": [ + { + "Key": "App", + "PropagateAtLaunch": true, + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "PropagateAtLaunch": true, + "Value": "TEST", + }, + { + "Key": "gu:repo", + "PropagateAtLaunch": true, + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "LogKinesisStreamName", + "PropagateAtLaunch": true, + "Value": { + "Ref": "LoggingStreamName", + }, + }, + { + "Key": "Stack", + "PropagateAtLaunch": true, + "Value": "frontend", + }, + { + "Key": "Stage", + "PropagateAtLaunch": true, + "Value": "CODE", + }, + { + "Key": "SystemdUnit", + "PropagateAtLaunch": true, + "Value": "interactive-rendering.service", + }, + ], + "TargetGroupARNs": [ + { + "Ref": "TargetGroupInteractiverenderingB3F5F6D3", + }, + ], + "VPCZoneIdentifier": { + "Ref": "interactiverenderingPrivateSubnets", + }, + }, + "Type": "AWS::AutoScaling::AutoScalingGroup", + }, + "CertificateInteractiverendering5C218F28": { + "DeletionPolicy": "Retain", + "Properties": { + "DomainName": "interactive-rendering.code.dev-guardianapis.com", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Name", + "Value": "InteractiveRendering-CODE/CertificateInteractiverendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + "ValidationMethod": "DNS", + }, + "Type": "AWS::CertificateManager::Certificate", + "UpdateReplacePolicy": "Retain", + }, + "DescribeEC2PolicyFF5F9295": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:DescribeAutoScalingGroups", + "ec2:DescribeTags", + "ec2:DescribeInstances", + ], + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "describe-ec2-policy", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "EcsService81FC6EF6": { + "DependsOn": [ + "EcsTaskDefinitionTaskRoleDefaultPolicy1FD2F057", + "EcsTaskDefinitionTaskRoleB7B6D8DD", + "ListenerInteractiverenderingDeterministicRouteToEc2Rule85263C36", + "ListenerInteractiverenderingDeterministicRouteToEcsRule3CF4950C", + "ListenerInteractiverenderingB2E016A6", + ], + "Properties": { + "Cluster": { + "Ref": "interactiverenderingEcsClusterEDBAEDA1", + }, + "DeploymentConfiguration": { + "Alarms": { + "AlarmNames": [], + "Enable": false, + "Rollback": false, + }, + "DeploymentCircuitBreaker": { + "Enable": true, + "Rollback": true, + }, + "MaximumPercent": 200, + "MinimumHealthyPercent": 100, + }, + "DeploymentController": { + "Type": "ECS", + }, + "EnableECSManagedTags": false, + "HealthCheckGracePeriodSeconds": 60, + "LaunchType": "FARGATE", + "LoadBalancers": [ + { + "ContainerName": "interactive-rendering", + "ContainerPort": 9000, + "TargetGroupArn": { + "Ref": "InteractiverenderingEcsTargetGroupInteractiverendering62D942CE", + }, + }, + ], + "Monitoring": { + "MetricConfigurations": [ + { + "MetricNames": [ + "CPUUtilization", + "MemoryUtilization", + ], + "ResolutionSeconds": 20, + }, + ], + }, + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverenderingecs077B4B99", + "GroupId", + ], + }, + ], + "Subnets": { + "Ref": "interactiverenderingPrivateSubnets", + }, + }, + }, + "PropagateTags": "SERVICE", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + "TaskDefinition": { + "Ref": "EcsTaskDefinition63157ED3", + }, + }, + "Type": "AWS::ECS::Service", + }, + "EcsServiceTaskCountTarget02FCCE22": { + "DependsOn": [ + "EcsTaskDefinitionTaskRoleDefaultPolicy1FD2F057", + "EcsTaskDefinitionTaskRoleB7B6D8DD", + ], + "Properties": { + "MaxCapacity": 3, + "MinCapacity": 1, + "ResourceId": { + "Fn::Join": [ + "", + [ + "service/", + { + "Ref": "interactiverenderingEcsClusterEDBAEDA1", + }, + "/", + { + "Fn::GetAtt": [ + "EcsService81FC6EF6", + "Name", + ], + }, + ], + ], + }, + "RoleARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":iam::", + { + "Ref": "AWS::AccountId", + }, + ":role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService", + ], + ], + }, + "ScalableDimension": "ecs:service:DesiredCount", + "ServiceNamespace": "ecs", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + }, + "Type": "AWS::ApplicationAutoScaling::ScalableTarget", + }, + "EcsTaskDefinition63157ED3": { + "Properties": { + "ContainerDefinitions": [ + { + "DependsOn": [ + { + "Condition": "START", + "ContainerName": "aws-otel-collector", + }, + ], + "DockerLabels": { + "RiffRaffDeploymentId": { + "Ref": "RiffRaffDeploymentId", + }, + }, + "Environment": [ + { + "Name": "STACK", + "Value": "frontend", + }, + { + "Name": "STAGE", + "Value": "CODE", + }, + { + "Name": "APP", + "Value": "interactive-rendering", + }, + { + "Name": "TASK_NAME", + "Value": "interactive-rendering", + }, + { + "Name": "GU_REPO", + "Value": "guardian/dotcom-rendering", + }, + { + "Name": "NODE_ENV", + "Value": "production", + }, + { + "Name": "GU_STAGE", + "Value": "CODE", + }, + { + "Name": "GU_APP", + "Value": "interactive-rendering", + }, + { + "Name": "GU_STACK", + "Value": "frontend", + }, + { + "Name": "OTEL_EXPORTER_OTLP_ENDPOINT", + "Value": "http://localhost:4318", + }, + { + "Name": "OTEL_SERVICE_NAME", + "Value": "interactive-rendering", + }, + ], + "Essential": true, + "Image": { + "Fn::Join": [ + "", + [ + { + "Ref": "ArtifactAccountIdParameter", + }, + ".dkr.ecr.eu-west-1.", + { + "Ref": "AWS::URLSuffix", + }, + "/guardian/dotcom-rendering/dotcom-rendering@sha256:12345", + ], + ], + }, + "LogConfiguration": { + "LogDriver": "awsfirelens", + "Options": { + "Name": "kinesis_streams", + "region": "eu-west-1", + "retry_limit": "2", + "stream": { + "Ref": "LoggingStreamName", + }, + }, + }, + "Name": "interactive-rendering", + "PortMappings": [ + { + "ContainerPort": 9000, + "Protocol": "tcp", + }, + ], + "ReadonlyRootFilesystem": true, + "VersionConsistency": "disabled", + }, + { + "Environment": [ + { + "Name": "STACK", + "Value": "frontend", + }, + { + "Name": "STAGE", + "Value": "CODE", + }, + { + "Name": "APP", + "Value": "interactive-rendering", + }, + { + "Name": "TASK_NAME", + "Value": "interactive-rendering", + }, + { + "Name": "GU_REPO", + "Value": "guardian/dotcom-rendering", + }, + ], + "Essential": true, + "FirelensConfiguration": { + "Type": "fluentbit", + }, + "Image": "ghcr.io/guardian/devx-logs@sha256:cf91724a5166f1c143e07958820aa2122afb61c164b68555d15cb92abb5acda0", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "EcsTaskDefinitionLogShippingLogGroup0E405BD0", + }, + "awslogs-region": "eu-west-1", + "awslogs-stream-prefix": "frontend/CODE/interactive-rendering/devx-logs-sidecar", + }, + }, + "MountPoints": [ + { + "ContainerPath": "/init", + "ReadOnly": false, + "SourceVolume": "logging-volume", + }, + ], + "Name": "LogShipping", + "ReadonlyRootFilesystem": true, + "VersionConsistency": "disabled", + }, + { + "Command": [ + "--config=/etc/ecs/ecs-default-config.yaml", + ], + "Cpu": 256, + "Essential": false, + "HealthCheck": { + "Command": [ + "CMD", + "/healthcheck", + ], + "Interval": 5, + "Retries": 2, + "Timeout": 3, + }, + "Image": "public.ecr.aws/aws-observability/aws-otel-collector@sha256:90b3180c21acb9497110480371a413ed91f2836077f8a8fb4507b019d3c481c0", + "LogConfiguration": { + "LogDriver": "awsfirelens", + "Options": { + "Name": "kinesis_streams", + "region": "eu-west-1", + "retry_limit": "2", + "stream": { + "Ref": "LoggingStreamName", + }, + }, + }, + "Memory": 512, + "Name": "aws-otel-collector", + "ReadonlyRootFilesystem": true, + }, + ], + "Cpu": "1024", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "EcsTaskDefinitionExecutionRoleBE450C73", + "Arn", + ], + }, + "Family": "InteractiveRenderingCODEEcsTaskDefinition0F808565", + "Memory": "2048", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE", + ], + "RuntimePlatform": { + "CpuArchitecture": "ARM64", + "OperatingSystemFamily": "LINUX", + }, + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "EcsTaskDefinitionTaskRoleB7B6D8DD", + "Arn", + ], + }, + "Volumes": [ + { + "Name": "logging-volume", + }, + ], + }, + "Type": "AWS::ECS::TaskDefinition", + }, + "EcsTaskDefinitionExecutionRoleBE450C73": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "EcsTaskDefinitionExecutionRoleDefaultPolicy1611A942": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ecr:eu-west-1:", + { + "Ref": "ArtifactAccountIdParameter", + }, + ":repository/guardian/dotcom-rendering/dotcom-rendering", + ], + ], + }, + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ], + "Effect": "Allow", + "Resource": "arn:aws:ecr:eu-west-1:694911143906:repository/aws-guardduty-agent-fargate", + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EcsTaskDefinitionLogShippingLogGroup0E405BD0", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "EcsTaskDefinitionExecutionRoleDefaultPolicy1611A942", + "Roles": [ + { + "Ref": "EcsTaskDefinitionExecutionRoleBE450C73", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "EcsTaskDefinitionLogShippingLogGroup0E405BD0": { + "DeletionPolicy": "Retain", + "Properties": { + "RetentionInDays": 1, + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "EcsTaskDefinitionTaskRoleB7B6D8DD": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "EcsTaskDefinitionTaskRoleDefaultPolicy1FD2F057": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:PutLogEvents", + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogStreams", + "logs:DescribeLogGroups", + "logs:PutRetentionPolicy", + "xray:PutTraceSegments", + "xray:PutTelemetryRecords", + "xray:GetSamplingRules", + "xray:GetSamplingTargets", + "xray:GetSamplingStatisticSummaries", + ], + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "EcsTaskDefinitionTaskRoleDefaultPolicy1FD2F057", + "Roles": [ + { + "Ref": "EcsTaskDefinitionTaskRoleB7B6D8DD", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "GetDistributablePolicyInteractiverendering381AFC49": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "DistributionBucketName", + }, + "/frontend/CODE/interactive-rendering/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "GetDistributablePolicyInteractiverendering381AFC49", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "GuHttpsEgressSecurityGroupInteractiverendering8AC409C4": { + "Properties": { + "GroupDescription": "Allow all outbound HTTPS traffic", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound HTTPS traffic", + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443, + }, + ], + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + "VpcId": { + "Ref": "VpcId", + }, + }, + "Type": "AWS::EC2::SecurityGroup", + }, + "GuHttpsEgressSecurityGroupInteractiverenderingecs077B4B99": { + "Properties": { + "GroupDescription": "Allow all outbound HTTPS traffic", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound HTTPS traffic", + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443, + }, + ], + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering-ecs", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + "VpcId": { + "Ref": "VpcId", + }, + }, + "Type": "AWS::EC2::SecurityGroup", + }, + "GuHttpsEgressSecurityGroupInteractiverenderingecsfromInteractiveRenderingCODEInternalIngressSecurityGroupInteractiverendering4C54400F9000D2A199BF": { + "Properties": { + "Description": "Load balancer to target", + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverenderingecs077B4B99", + "GroupId", + ], + }, + "IpProtocol": "tcp", + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "InternalIngressSecurityGroupInteractiverendering71ED4A41", + "GroupId", + ], + }, + "ToPort": 9000, + }, + "Type": "AWS::EC2::SecurityGroupIngress", + }, + "GuHttpsEgressSecurityGroupInteractiverenderingecsfromInteractiveRenderingCODELoadBalancerInteractiverenderingSecurityGroup11A1BC249000B016B467": { + "Properties": { + "Description": "Load balancer to target", + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverenderingecs077B4B99", + "GroupId", + ], + }, + "IpProtocol": "tcp", + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingSecurityGroupBDDFF9AB", + "GroupId", + ], + }, + "ToPort": 9000, + }, + "Type": "AWS::EC2::SecurityGroupIngress", + }, + "GuHttpsEgressSecurityGroupInteractiverenderingfromInteractiveRenderingCODEInternalIngressSecurityGroupInteractiverendering4C54400F9000604E8775": { + "Properties": { + "Description": "Load balancer to target", + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverendering8AC409C4", + "GroupId", + ], + }, + "IpProtocol": "tcp", + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "InternalIngressSecurityGroupInteractiverendering71ED4A41", + "GroupId", + ], + }, + "ToPort": 9000, + }, + "Type": "AWS::EC2::SecurityGroupIngress", + }, + "GuHttpsEgressSecurityGroupInteractiverenderingfromInteractiveRenderingCODELoadBalancerInteractiverenderingSecurityGroup11A1BC2490000A31C01B": { + "Properties": { + "Description": "Load balancer to target", + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverendering8AC409C4", + "GroupId", + ], + }, + "IpProtocol": "tcp", + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingSecurityGroupBDDFF9AB", + "GroupId", + ], + }, + "ToPort": 9000, + }, + "Type": "AWS::EC2::SecurityGroupIngress", + }, + "GuLogShippingPolicy981BFE5A": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "kinesis:Describe*", + "kinesis:Put*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:kinesis:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":stream/", + { + "Ref": "LoggingStreamName", + }, + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "GuLogShippingPolicy981BFE5A", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + { + "Ref": "EcsTaskDefinitionTaskRoleB7B6D8DD", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "InstanceRoleInteractiverenderingA0F05C94": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Path": "/", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "InteractiverenderingEcsTargetGroupInteractiverendering62D942CE": { + "Properties": { + "HealthCheckIntervalSeconds": 10, + "HealthCheckPath": "/_healthcheck", + "HealthCheckProtocol": "HTTP", + "HealthCheckTimeoutSeconds": 5, + "HealthyThresholdCount": 5, + "Port": 9000, + "Protocol": "HTTP", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + "TargetGroupAttributes": [ + { + "Key": "deregistration_delay.timeout_seconds", + "Value": "30", + }, + { + "Key": "stickiness.enabled", + "Value": "false", + }, + ], + "TargetType": "ip", + "UnhealthyThresholdCount": 2, + "VpcId": { + "Ref": "VpcId", + }, + }, + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + }, + "InternalIngressSecurityGroupInteractiverendering71ED4A41": { + "Properties": { + "GroupDescription": "Allow restricted ingress from CIDR ranges", + "SecurityGroupIngress": [ + { + "CidrIp": "10.0.0.0/8", + "Description": "Allow access on port 443 from 10.0.0.0/8", + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443, + }, + ], + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + "VpcId": { + "Ref": "VpcId", + }, + }, + "Type": "AWS::EC2::SecurityGroup", + }, + "InternalIngressSecurityGroupInteractiverenderingtoInteractiveRenderingCODEGuHttpsEgressSecurityGroupInteractiverenderingE68466639000AF9FB151": { + "Properties": { + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverendering8AC409C4", + "GroupId", + ], + }, + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "InternalIngressSecurityGroupInteractiverendering71ED4A41", + "GroupId", + ], + }, + "IpProtocol": "tcp", + "ToPort": 9000, + }, + "Type": "AWS::EC2::SecurityGroupEgress", + }, + "InternalIngressSecurityGroupInteractiverenderingtoInteractiveRenderingCODEGuHttpsEgressSecurityGroupInteractiverenderingecs8283E2219000B7C80545": { + "Properties": { + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverenderingecs077B4B99", + "GroupId", + ], + }, + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "InternalIngressSecurityGroupInteractiverendering71ED4A41", + "GroupId", + ], + }, + "IpProtocol": "tcp", + "ToPort": 9000, + }, + "Type": "AWS::EC2::SecurityGroupEgress", + }, + "ListenerInteractiverenderingB2E016A6": { + "Properties": { + "Certificates": [ + { + "CertificateArn": { + "Ref": "CertificateInteractiverendering5C218F28", + }, + }, + ], + "DefaultActions": [ + { + "ForwardConfig": { + "TargetGroups": [ + { + "TargetGroupArn": { + "Ref": "TargetGroupInteractiverenderingB3F5F6D3", + }, + "Weight": 1, + }, + { + "TargetGroupArn": { + "Ref": "InteractiverenderingEcsTargetGroupInteractiverendering62D942CE", + }, + "Weight": 0, + }, + ], + }, + "Type": "forward", + }, + ], + "LoadBalancerArn": { + "Ref": "LoadBalancerInteractiverenderingB53453B9", + }, + "Port": 443, + "Protocol": "HTTPS", + "SslPolicy": "ELBSecurityPolicy-TLS13-1-2-2021-06", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + }, + "Type": "AWS::ElasticLoadBalancingV2::Listener", + }, + "ListenerInteractiverenderingDeterministicRouteToEc2Rule85263C36": { + "Properties": { + "Actions": [ + { + "TargetGroupArn": { + "Ref": "TargetGroupInteractiverenderingB3F5F6D3", + }, + "Type": "forward", + }, + ], + "Conditions": [ + { + "Field": "http-header", + "HttpHeaderConfig": { + "HttpHeaderName": "X-Gu-Target-Group", + "Values": [ + "ec2", + ], + }, + }, + ], + "ListenerArn": { + "Ref": "ListenerInteractiverenderingB2E016A6", + }, + "Priority": 10, + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + }, + "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", + }, + "ListenerInteractiverenderingDeterministicRouteToEcsRule3CF4950C": { + "Properties": { + "Actions": [ + { + "TargetGroupArn": { + "Ref": "InteractiverenderingEcsTargetGroupInteractiverendering62D942CE", + }, + "Type": "forward", + }, + ], + "Conditions": [ + { + "Field": "http-header", + "HttpHeaderConfig": { + "HttpHeaderName": "X-Gu-Target-Group", + "Values": [ + "ecs", + ], + }, + }, + ], + "ListenerArn": { + "Ref": "ListenerInteractiverenderingB2E016A6", + }, + "Priority": 11, + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + }, + "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", + }, + "LoadBalancerDNS": { + "Properties": { + "Name": "interactive-rendering.code.dev-guardianapis.com", + "RecordType": "CNAME", + "ResourceRecords": [ + { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingB53453B9", + "DNSName", + ], + }, + ], + "Stage": "CODE", + "TTL": 3600, + }, + "Type": "Guardian::DNS::RecordSet", + }, + "LoadBalancerInteractiverenderingB53453B9": { + "Properties": { + "LoadBalancerAttributes": [ + { + "Key": "deletion_protection.enabled", + "Value": "true", + }, + { + "Key": "routing.http.x_amzn_tls_version_and_cipher_suite.enabled", + "Value": "true", + }, + { + "Key": "routing.http.drop_invalid_header_fields.enabled", + "Value": "true", + }, + { + "Key": "access_logs.s3.enabled", + "Value": "true", + }, + { + "Key": "access_logs.s3.bucket", + "Value": { + "Ref": "AccessLoggingBucket", + }, + }, + { + "Key": "access_logs.s3.prefix", + "Value": "application-load-balancer/CODE/frontend/interactive-rendering", + }, + { + "Key": "idle_timeout.timeout_seconds", + "Value": "4", + }, + ], + "Scheme": "internal", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingSecurityGroupBDDFF9AB", + "GroupId", + ], + }, + { + "Fn::GetAtt": [ + "InternalIngressSecurityGroupInteractiverendering71ED4A41", + "GroupId", + ], + }, + ], + "Subnets": { + "Ref": "interactiverenderingPrivateSubnets", + }, + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + "Type": "application", + }, + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + }, + "LoadBalancerInteractiverenderingSecurityGroupBDDFF9AB": { + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB InteractiveRenderingCODELoadBalancerInteractiverendering5931855D", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + "VpcId": { + "Ref": "VpcId", + }, + }, + "Type": "AWS::EC2::SecurityGroup", + }, + "LoadBalancerInteractiverenderingSecurityGrouptoInteractiveRenderingCODEGuHttpsEgressSecurityGroupInteractiverenderingE68466639000550ED22B": { + "Properties": { + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverendering8AC409C4", + "GroupId", + ], + }, + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingSecurityGroupBDDFF9AB", + "GroupId", + ], + }, + "IpProtocol": "tcp", + "ToPort": 9000, + }, + "Type": "AWS::EC2::SecurityGroupEgress", + }, + "LoadBalancerInteractiverenderingSecurityGrouptoInteractiveRenderingCODEGuHttpsEgressSecurityGroupInteractiverenderingecs8283E221900026C6441D": { + "Properties": { + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverenderingecs077B4B99", + "GroupId", + ], + }, + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingSecurityGroupBDDFF9AB", + "GroupId", + ], + }, + "IpProtocol": "tcp", + "ToPort": 9000, + }, + "Type": "AWS::EC2::SecurityGroupEgress", + }, + "ParameterStoreReadInteractiverendering73443FBD": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "ssm:GetParametersByPath", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":parameter/CODE/frontend/interactive-rendering", + ], + ], + }, + }, + { + "Action": [ + "ssm:GetParameters", + "ssm:GetParameter", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":parameter/CODE/frontend/interactive-rendering/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "parameter-store-read-policy", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + { + "Ref": "EcsTaskDefinitionTaskRoleB7B6D8DD", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "RenderingBaseURLParamAE11E777": { + "Properties": { + "Description": "The rendering base URL for frontend to call the interactive-rendering app in the CODE environment", + "Name": "/frontend/code/interactive-rendering.baseURL", + "Tags": { + "App": "interactive-rendering", + "Stack": "frontend", + "Stage": "CODE", + "gu:cdk:version": "TEST", + "gu:repo": "guardian/dotcom-rendering", + }, + "Type": "String", + "Value": "https://interactive-rendering.code.dev-guardianapis.com", + }, + "Type": "AWS::SSM::Parameter", + }, + "SsmSshPolicy4CFC977E": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2messages:AcknowledgeMessage", + "ec2messages:DeleteMessage", + "ec2messages:FailMessage", + "ec2messages:GetEndpoint", + "ec2messages:GetMessages", + "ec2messages:SendReply", + "ssm:UpdateInstanceInformation", + "ssm:ListInstanceAssociations", + "ssm:DescribeInstanceProperties", + "ssm:DescribeDocumentParameters", + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel", + ], + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "ssm-ssh-policy", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "TargetGroupInteractiverenderingB3F5F6D3": { + "Properties": { + "HealthCheckIntervalSeconds": 10, + "HealthCheckPath": "/_healthcheck", + "HealthCheckProtocol": "HTTP", + "HealthCheckTimeoutSeconds": 5, + "HealthyThresholdCount": 5, + "Port": 9000, + "Protocol": "HTTP", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + "TargetGroupAttributes": [ + { + "Key": "deregistration_delay.timeout_seconds", + "Value": "30", + }, + { + "Key": "stickiness.enabled", + "Value": "false", + }, + ], + "TargetType": "instance", + "UnhealthyThresholdCount": 2, + "VpcId": { + "Ref": "VpcId", + }, + }, + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + }, + "frontendCODEinteractiverenderingD1F3B137": { + "DependsOn": [ + "InstanceRoleInteractiverenderingA0F05C94", + ], + "Properties": { + "LaunchTemplateData": { + "IamInstanceProfile": { + "Arn": { + "Fn::GetAtt": [ + "frontendCODEinteractiverenderingProfileB86599B4", + "Arn", + ], + }, + }, + "ImageId": { + "Ref": "AMIInteractiverendering", + }, + "InstanceType": "t4g.micro", + "MetadataOptions": { + "HttpTokens": "required", + "InstanceMetadataTags": "enabled", + }, + "Monitoring": { + "Enabled": true, + }, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverendering8AC409C4", + "GroupId", + ], + }, + ], + "TagSpecifications": [ + { + "ResourceType": "instance", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Name", + "Value": "InteractiveRendering-CODE/frontend-CODE-interactive-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + }, + { + "ResourceType": "volume", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Name", + "Value": "InteractiveRendering-CODE/frontend-CODE-interactive-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + }, + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash +set -ev +groupadd frontend +useradd -r -m -s /usr/bin/nologin -g frontend dotcom-rendering +cd /home/dotcom-rendering +aws --region eu-west-1 s3 cp s3://", + { + "Ref": "DistributionBucketName", + }, + "/frontend/CODE/interactive-rendering/interactive-rendering.tar.gz ./ +tar -zxf interactive-rendering.tar.gz interactive-rendering +chown -R dotcom-rendering:frontend interactive-rendering +cd interactive-rendering +mkdir /var/log/dotcom-rendering +chown -R dotcom-rendering:frontend /var/log/dotcom-rendering +cat > /etc/systemd/system/interactive-rendering.service << EOF +[Unit] +Description=interactive-rendering +After=network.target +[Service] +WorkingDirectory=/home/dotcom-rendering/interactive-rendering +Type=simple +User=dotcom-rendering +Group=frontend +StandardError=journal +StandardOutput=journal +Environment=TERM=xterm-256color +Environment=NODE_ENV=production +Environment=GU_STAGE=CODE +Environment=GU_APP=interactive-rendering +Environment=GU_STACK=frontend +ExecStart=make prod +Restart=on-failure +[Install] +WantedBy=multi-user.target +EOF +systemctl enable interactive-rendering +systemctl start interactive-rendering", + ], + ], + }, + }, + }, + "TagSpecifications": [ + { + "ResourceType": "launch-template", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Name", + "Value": "InteractiveRendering-CODE/frontend-CODE-interactive-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + }, + ], + }, + "Type": "AWS::EC2::LaunchTemplate", + }, + "frontendCODEinteractiverenderingProfileB86599B4": { + "Properties": { + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::InstanceProfile", + }, + "interactiverenderingCpuScaling89946FA4": { + "Properties": { + "PolicyName": "InteractiveRenderingCODEinteractiverenderingCpuScaling00B862C4", + "PolicyType": "TargetTrackingScaling", + "ScalingTargetId": { + "Ref": "EcsServiceTaskCountTarget02FCCE22", + }, + "TargetTrackingScalingPolicyConfiguration": { + "PredefinedMetricSpecification": { + "PredefinedMetricType": "ECSServiceAverageCPUUtilizationHighResolution", + }, + "ScaleInCooldown": 60, + "ScaleOutCooldown": 60, + "TargetValue": 20, + }, + }, + "Type": "AWS::ApplicationAutoScaling::ScalingPolicy", + }, + "interactiverenderingEcsClusterEDBAEDA1": { + "Properties": { + "ClusterSettings": [ + { + "Name": "containerInsights", + "Value": "enhanced", + }, + ], + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "CODE", + }, + ], + }, + "Type": "AWS::ECS::Cluster", + }, + }, +} +`; + +exports[`The RenderingCDKStack matches the snapshot for Interactive Rendering PROD (not using ECS) 1`] = ` +{ + "Metadata": { + "gu:cdk:constructs": [ + "GuDistributionBucketParameter", + "GuAllowPolicy", + "GuAllowPolicy", + "GuAllowPolicy", + "GuVpcParameter", + "GuSubnetListParameter", + "GuSubnetListParameter", + "GuLoadBalancedAppExperimental", + "GuInstanceRole", + "GuSsmSshPolicy", + "GuDescribeEC2Policy", + "GuLoggingStreamNameParameter", + "GuLogShippingPolicy", + "GuGetDistributablePolicy", + "GuParameterStoreReadPolicy", + "GuAmiParameter", + "GuHttpsEgressSecurityGroup", + "GuAutoScalingGroup", + "GuApplicationTargetGroup", + "GuCertificate", + "GuApplicationLoadBalancer", + "GuAccessLoggingBucketParameter", + "GuHttpsApplicationListener", + "GuSecurityGroup", + "GuAlb5xxPercentageAlarm", + "GuUnhealthyInstancesAlarm", + "GuCname", + ], + "gu:cdk:version": "TEST", + }, + "Outputs": { + "LoadBalancerInteractiverenderingDnsName": { + "Description": "DNS entry for LoadBalancerInteractiverendering", + "Value": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingB53453B9", + "DNSName", + ], + }, + }, + }, + "Parameters": { + "AMIInteractiverendering": { + "Description": "Amazon Machine Image ID for the app interactive-rendering. Use this in conjunction with AMIgo to keep AMIs up to date.", + "Type": "AWS::EC2::Image::Id", + }, + "AccessLoggingBucket": { + "Default": "/account/services/access-logging/bucket", + "Description": "S3 bucket to store your access logs", + "Type": "AWS::SSM::Parameter::Value", + }, + "DistributionBucketName": { + "Default": "/account/services/artifact.bucket", + "Description": "SSM parameter containing the S3 bucket name holding distribution artifacts", + "Type": "AWS::SSM::Parameter::Value", + }, + "LoggingStreamName": { + "Default": "/account/services/logging.stream.name", + "Description": "SSM parameter containing the Name (not ARN) on the kinesis stream", + "Type": "AWS::SSM::Parameter::Value", + }, + "VpcId": { + "Default": "/account/vpc/primary/id", + "Description": "Virtual Private Cloud to run EC2 instances within. Should NOT be the account default VPC.", + "Type": "AWS::SSM::Parameter::Value", + }, + "interactiverenderingPrivateSubnets": { + "Default": "/account/vpc/primary/subnets/private", + "Description": "A list of private subnets", + "Type": "AWS::SSM::Parameter::Value>", + }, + "interactiverenderingPublicSubnets": { + "Default": "/account/vpc/primary/subnets/public", + "Description": "A list of public subnets", + "Type": "AWS::SSM::Parameter::Value>", + }, + }, + "Resources": { + "AllowPolicyCloudwatchLogsA783E5B4": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cloudwatch:*", + "logs:*", + ], + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AllowPolicyCloudwatchLogsA783E5B4", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "AllowPolicyDescribeDecryptKmsE91286F3": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:kms:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":FrontendConfigKey", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AllowPolicyDescribeDecryptKmsE91286F3", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "AllowPolicyGetSsmParamsByPathB54B2DE8": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ssm:GetParametersByPath", + "ssm:GetParameter", + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":parameter/frontend/*", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":parameter/dotcom/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "AllowPolicyGetSsmParamsByPathB54B2DE8", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "AutoScalingGroupInteractiverenderingASGCDA22918": { + "Properties": { + "HealthCheckGracePeriod": 120, + "HealthCheckType": "ELB", + "LaunchTemplate": { + "LaunchTemplateId": { + "Ref": "frontendPRODinteractiverenderingEABC55EF", + }, + "Version": { + "Fn::GetAtt": [ + "frontendPRODinteractiverenderingEABC55EF", + "LatestVersionNumber", + ], + }, + }, + "MaxSize": "30", + "MetricsCollection": [ + { + "Granularity": "1Minute", + }, + ], + "MinSize": "3", + "Tags": [ + { + "Key": "App", + "PropagateAtLaunch": true, + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "PropagateAtLaunch": true, + "Value": "TEST", + }, + { + "Key": "gu:repo", + "PropagateAtLaunch": true, + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "LogKinesisStreamName", + "PropagateAtLaunch": true, + "Value": { + "Ref": "LoggingStreamName", + }, + }, + { + "Key": "Stack", + "PropagateAtLaunch": true, + "Value": "frontend", + }, + { + "Key": "Stage", + "PropagateAtLaunch": true, + "Value": "PROD", + }, + { + "Key": "SystemdUnit", + "PropagateAtLaunch": true, + "Value": "interactive-rendering.service", + }, + ], + "TargetGroupARNs": [ + { + "Ref": "TargetGroupInteractiverenderingB3F5F6D3", + }, + ], + "VPCZoneIdentifier": { + "Ref": "interactiverenderingPrivateSubnets", + }, + }, + "Type": "AWS::AutoScaling::AutoScalingGroup", + }, + "CPUScaleUpPolicyUpperAlarm7E15DD91": { + "Properties": { + "AlarmActions": [ + { + "Ref": "CPUScaleUpPolicyUpperPolicyDEE5DA77", + }, + { + "Ref": "CPUScalingAlertsTopic9D0083F7", + }, + ], + "AlarmDescription": "Upper threshold scaling alarm", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "Dimensions": [ + { + "Name": "AutoScalingGroupName", + "Value": { + "Ref": "AutoScalingGroupInteractiverenderingASGCDA22918", + }, + }, + ], + "EvaluationPeriods": 2, + "ExtendedStatistic": "p90", + "MetricName": "CPUUtilization", + "Namespace": "AWS/EC2", + "Period": 30, + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + "Threshold": 60, + "Unit": "Percent", + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "CPUScaleUpPolicyUpperPolicyDEE5DA77": { + "Properties": { + "AdjustmentType": "PercentChangeInCapacity", + "AutoScalingGroupName": { + "Ref": "AutoScalingGroupInteractiverenderingASGCDA22918", + }, + "MetricAggregationType": "Average", + "PolicyType": "StepScaling", + "StepAdjustments": [ + { + "MetricIntervalLowerBound": 0, + "MetricIntervalUpperBound": 20, + "ScalingAdjustment": 50, + }, + { + "MetricIntervalLowerBound": 20, + "ScalingAdjustment": 80, + }, + ], + }, + "Type": "AWS::AutoScaling::ScalingPolicy", + }, + "CPUScalingAlertsSubscriptionEmail170D6D82": { + "Properties": { + "Endpoint": "dotcom.platform@theguardian.com", + "Protocol": "email", + "TopicArn": { + "Ref": "CPUScalingAlertsTopic9D0083F7", + }, + }, + "Type": "AWS::SNS::Subscription", + }, + "CPUScalingAlertsTopic9D0083F7": { + "Properties": { + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + }, + "Type": "AWS::SNS::Topic", + }, + "CertificateInteractiverendering5C218F28": { + "DeletionPolicy": "Retain", + "Properties": { + "DomainName": "interactive-rendering.guardianapis.com", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Name", + "Value": "InteractiveRendering-PROD/CertificateInteractiverendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + "ValidationMethod": "DNS", + }, + "Type": "AWS::CertificateManager::Certificate", + "UpdateReplacePolicy": "Retain", + }, + "DescribeEC2PolicyFF5F9295": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:DescribeAutoScalingGroups", + "ec2:DescribeTags", + "ec2:DescribeInstances", + ], + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "describe-ec2-policy", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "GetDistributablePolicyInteractiverendering381AFC49": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "DistributionBucketName", + }, + "/frontend/PROD/interactive-rendering/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "GetDistributablePolicyInteractiverendering381AFC49", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "GuHttpsEgressSecurityGroupInteractiverendering8AC409C4": { + "Properties": { + "GroupDescription": "Allow all outbound HTTPS traffic", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound HTTPS traffic", + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443, + }, + ], + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + "VpcId": { + "Ref": "VpcId", + }, + }, + "Type": "AWS::EC2::SecurityGroup", + }, + "GuHttpsEgressSecurityGroupInteractiverenderingfromInteractiveRenderingPRODInternalIngressSecurityGroupInteractiverendering4EAEE1A3900095B63859": { + "Properties": { + "Description": "Load balancer to target", + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverendering8AC409C4", + "GroupId", + ], + }, + "IpProtocol": "tcp", + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "InternalIngressSecurityGroupInteractiverendering71ED4A41", + "GroupId", + ], + }, + "ToPort": 9000, + }, + "Type": "AWS::EC2::SecurityGroupIngress", + }, + "GuHttpsEgressSecurityGroupInteractiverenderingfromInteractiveRenderingPRODLoadBalancerInteractiverenderingSecurityGroup485B80A49000041FB020": { + "Properties": { + "Description": "Load balancer to target", + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverendering8AC409C4", + "GroupId", + ], + }, + "IpProtocol": "tcp", + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingSecurityGroupBDDFF9AB", + "GroupId", + ], + }, + "ToPort": 9000, + }, + "Type": "AWS::EC2::SecurityGroupIngress", + }, + "GuLogShippingPolicy981BFE5A": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "kinesis:Describe*", + "kinesis:Put*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:kinesis:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":stream/", + { + "Ref": "LoggingStreamName", + }, + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "GuLogShippingPolicy981BFE5A", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "High5xxPercentageAlarmInteractiverendering279B163E": { + "Properties": { + "ActionsEnabled": true, + "AlarmActions": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:sns:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":Frontend-PROD-CriticalAlerts", + ], + ], + }, + ], + "AlarmDescription": "interactive-rendering exceeded 0.5% error rate", + "AlarmName": "High 5XX error percentage from interactive-rendering in PROD", + "ComparisonOperator": "GreaterThanThreshold", + "EvaluationPeriods": 1, + "Metrics": [ + { + "Expression": "100*(m1+m2)/m3", + "Id": "expr_1", + "Label": "% of 5XX responses served for interactive-rendering (load balancer and instances combined)", + "ReturnData": true, + }, + { + "Id": "m1", + "MetricStat": { + "Metric": { + "Dimensions": [ + { + "Name": "LoadBalancer", + "Value": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingB53453B9", + "LoadBalancerFullName", + ], + }, + }, + ], + "MetricName": "HTTPCode_ELB_5XX_Count", + "Namespace": "AWS/ApplicationELB", + }, + "Period": 60, + "Stat": "Sum", + }, + "ReturnData": false, + }, + { + "Id": "m2", + "MetricStat": { + "Metric": { + "Dimensions": [ + { + "Name": "LoadBalancer", + "Value": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingB53453B9", + "LoadBalancerFullName", + ], + }, + }, + ], + "MetricName": "HTTPCode_Target_5XX_Count", + "Namespace": "AWS/ApplicationELB", + }, + "Period": 60, + "Stat": "Sum", + }, + "ReturnData": false, + }, + { + "Id": "m3", + "MetricStat": { + "Metric": { + "Dimensions": [ + { + "Name": "LoadBalancer", + "Value": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingB53453B9", + "LoadBalancerFullName", + ], + }, + }, + ], + "MetricName": "RequestCount", + "Namespace": "AWS/ApplicationELB", + }, + "Period": 60, + "Stat": "Sum", + }, + "ReturnData": false, + }, + ], + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + "Threshold": 0.5, + "TreatMissingData": "notBreaching", + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "InstanceRoleInteractiverenderingA0F05C94": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Path": "/", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "InternalIngressSecurityGroupInteractiverendering71ED4A41": { + "Properties": { + "GroupDescription": "Allow restricted ingress from CIDR ranges", + "SecurityGroupIngress": [ + { + "CidrIp": "10.0.0.0/8", + "Description": "Allow access on port 443 from 10.0.0.0/8", + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443, + }, + ], + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + "VpcId": { + "Ref": "VpcId", + }, + }, + "Type": "AWS::EC2::SecurityGroup", + }, + "InternalIngressSecurityGroupInteractiverenderingtoInteractiveRenderingPRODGuHttpsEgressSecurityGroupInteractiverendering7D2C46D0900008151B0F": { + "Properties": { + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverendering8AC409C4", + "GroupId", + ], + }, + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "InternalIngressSecurityGroupInteractiverendering71ED4A41", + "GroupId", + ], + }, + "IpProtocol": "tcp", + "ToPort": 9000, + }, + "Type": "AWS::EC2::SecurityGroupEgress", + }, + "LatencyScaleDownPolicyLowerAlarm3AAA79CF": { + "Properties": { + "AlarmActions": [ + { + "Ref": "LatencyScaleDownPolicyLowerPolicy5B8C5A01", + }, + ], + "AlarmDescription": "Lower threshold scaling alarm", + "ComparisonOperator": "LessThanOrEqualToThreshold", + "Dimensions": [ + { + "Name": "LoadBalancer", + "Value": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingB53453B9", + "LoadBalancerFullName", + ], + }, + }, + { + "Name": "TargetGroup", + "Value": { + "Fn::GetAtt": [ + "TargetGroupInteractiverenderingB3F5F6D3", + "TargetGroupFullName", + ], + }, + }, + ], + "EvaluationPeriods": 10, + "MetricName": "TargetResponseTime", + "Namespace": "AWS/ApplicationELB", + "Period": 30, + "Statistic": "Average", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + "Threshold": 0.15, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "LatencyScaleDownPolicyLowerPolicy5B8C5A01": { + "Properties": { + "AdjustmentType": "ChangeInCapacity", + "AutoScalingGroupName": { + "Ref": "AutoScalingGroupInteractiverenderingASGCDA22918", + }, + "MetricAggregationType": "Average", + "PolicyType": "StepScaling", + "StepAdjustments": [ + { + "MetricIntervalUpperBound": 0, + "ScalingAdjustment": -1, + }, + ], + }, + "Type": "AWS::AutoScaling::ScalingPolicy", + }, + "LatencyScaleUpPolicyUpperAlarmB2CCE62D": { + "Properties": { + "AlarmActions": [ + { + "Ref": "LatencyScaleUpPolicyUpperPolicy4A22B34C", + }, + { + "Ref": "LatencyScalingAlertsTopic1A322157", + }, + ], + "AlarmDescription": "Upper threshold scaling alarm", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "Dimensions": [ + { + "Name": "LoadBalancer", + "Value": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingB53453B9", + "LoadBalancerFullName", + ], + }, + }, + { + "Name": "TargetGroup", + "Value": { + "Fn::GetAtt": [ + "TargetGroupInteractiverenderingB3F5F6D3", + "TargetGroupFullName", + ], + }, + }, + ], + "EvaluationPeriods": 2, + "MetricName": "TargetResponseTime", + "Namespace": "AWS/ApplicationELB", + "Period": 30, + "Statistic": "Average", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + "Threshold": 0.2, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "LatencyScaleUpPolicyUpperPolicy4A22B34C": { + "Properties": { + "AdjustmentType": "PercentChangeInCapacity", + "AutoScalingGroupName": { + "Ref": "AutoScalingGroupInteractiverenderingASGCDA22918", + }, + "MetricAggregationType": "Average", + "PolicyType": "StepScaling", + "StepAdjustments": [ + { + "MetricIntervalLowerBound": 0, + "MetricIntervalUpperBound": 0.09999999999999998, + "ScalingAdjustment": 50, + }, + { + "MetricIntervalLowerBound": 0.09999999999999998, + "ScalingAdjustment": 80, + }, + ], + }, + "Type": "AWS::AutoScaling::ScalingPolicy", + }, + "LatencyScalingAlertsSubscriptionEmailF1826DAB": { + "Properties": { + "Endpoint": "dotcom.platform@theguardian.com", + "Protocol": "email", + "TopicArn": { + "Ref": "LatencyScalingAlertsTopic1A322157", + }, + }, + "Type": "AWS::SNS::Subscription", + }, + "LatencyScalingAlertsTopic1A322157": { + "Properties": { + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + }, + "Type": "AWS::SNS::Topic", + }, + "ListenerInteractiverenderingB2E016A6": { + "Properties": { + "Certificates": [ + { + "CertificateArn": { + "Ref": "CertificateInteractiverendering5C218F28", + }, + }, + ], + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "TargetGroupInteractiverenderingB3F5F6D3", + }, + "Type": "forward", + }, + ], + "LoadBalancerArn": { + "Ref": "LoadBalancerInteractiverenderingB53453B9", + }, + "Port": 443, + "Protocol": "HTTPS", + "SslPolicy": "ELBSecurityPolicy-TLS13-1-2-2021-06", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + }, + "Type": "AWS::ElasticLoadBalancingV2::Listener", + }, + "LoadBalancerDNS": { + "Properties": { + "Name": "interactive-rendering.guardianapis.com", + "RecordType": "CNAME", + "ResourceRecords": [ + { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingB53453B9", + "DNSName", + ], + }, + ], + "Stage": "PROD", + "TTL": 3600, + }, + "Type": "Guardian::DNS::RecordSet", + }, + "LoadBalancerInteractiverenderingB53453B9": { + "Properties": { + "LoadBalancerAttributes": [ + { + "Key": "deletion_protection.enabled", + "Value": "true", + }, + { + "Key": "routing.http.x_amzn_tls_version_and_cipher_suite.enabled", + "Value": "true", + }, + { + "Key": "routing.http.drop_invalid_header_fields.enabled", + "Value": "true", + }, + { + "Key": "access_logs.s3.enabled", + "Value": "true", + }, + { + "Key": "access_logs.s3.bucket", + "Value": { + "Ref": "AccessLoggingBucket", + }, + }, + { + "Key": "access_logs.s3.prefix", + "Value": "application-load-balancer/PROD/frontend/interactive-rendering", + }, + { + "Key": "idle_timeout.timeout_seconds", + "Value": "4", + }, + ], + "Scheme": "internal", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingSecurityGroupBDDFF9AB", + "GroupId", + ], + }, + { + "Fn::GetAtt": [ + "InternalIngressSecurityGroupInteractiverendering71ED4A41", + "GroupId", + ], + }, + ], + "Subnets": { + "Ref": "interactiverenderingPrivateSubnets", + }, + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + "Type": "application", + }, + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + }, + "LoadBalancerInteractiverenderingSecurityGroupBDDFF9AB": { + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB InteractiveRenderingPRODLoadBalancerInteractiverendering124B36A8", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + "VpcId": { + "Ref": "VpcId", + }, + }, + "Type": "AWS::EC2::SecurityGroup", + }, + "LoadBalancerInteractiverenderingSecurityGrouptoInteractiveRenderingPRODGuHttpsEgressSecurityGroupInteractiverendering7D2C46D09000634366C2": { + "Properties": { + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverendering8AC409C4", + "GroupId", + ], + }, + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "LoadBalancerInteractiverenderingSecurityGroupBDDFF9AB", + "GroupId", + ], + }, + "IpProtocol": "tcp", + "ToPort": 9000, + }, + "Type": "AWS::EC2::SecurityGroupEgress", + }, + "ParameterStoreReadInteractiverendering73443FBD": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "ssm:GetParametersByPath", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":parameter/PROD/frontend/interactive-rendering", + ], + ], + }, + }, + { + "Action": [ + "ssm:GetParameters", + "ssm:GetParameter", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ssm:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":parameter/PROD/frontend/interactive-rendering/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "parameter-store-read-policy", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "RenderingBaseURLParamAE11E777": { + "Properties": { + "Description": "The rendering base URL for frontend to call the interactive-rendering app in the PROD environment", + "Name": "/frontend/prod/interactive-rendering.baseURL", + "Tags": { + "App": "interactive-rendering", + "Stack": "frontend", + "Stage": "PROD", + "gu:cdk:version": "TEST", + "gu:repo": "guardian/dotcom-rendering", + }, + "Type": "String", + "Value": "https://interactive-rendering.guardianapis.com", + }, + "Type": "AWS::SSM::Parameter", + }, + "SsmSshPolicy4CFC977E": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2messages:AcknowledgeMessage", + "ec2messages:DeleteMessage", + "ec2messages:FailMessage", + "ec2messages:GetEndpoint", + "ec2messages:GetMessages", + "ec2messages:SendReply", + "ssm:UpdateInstanceInformation", + "ssm:ListInstanceAssociations", + "ssm:DescribeInstanceProperties", + "ssm:DescribeDocumentParameters", + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel", + ], + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "ssm-ssh-policy", + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "TargetGroupInteractiverenderingB3F5F6D3": { + "Properties": { + "HealthCheckIntervalSeconds": 10, + "HealthCheckPath": "/_healthcheck", + "HealthCheckProtocol": "HTTP", + "HealthCheckTimeoutSeconds": 5, + "HealthyThresholdCount": 5, + "Port": 9000, + "Protocol": "HTTP", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + "TargetGroupAttributes": [ + { + "Key": "deregistration_delay.timeout_seconds", + "Value": "30", + }, + { + "Key": "stickiness.enabled", + "Value": "false", + }, + ], + "TargetType": "instance", + "UnhealthyThresholdCount": 2, + "VpcId": { + "Ref": "VpcId", + }, + }, + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + }, + "UnhealthyInstancesAlarmInteractiverendering51E66D23": { + "Properties": { + "ActionsEnabled": true, + "AlarmActions": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:sns:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":Frontend-PROD-CriticalAlerts", + ], + ], + }, + ], + "AlarmDescription": "interactive-rendering's instances have failed healthchecks several times over the last 1 hour. + This typically results in the AutoScaling Group cycling instances and can lead to problems with deployment, + scaling or handling traffic spikes. + + Check interactive-rendering's application logs or ssh onto an unhealthy instance in order to debug these problems.", + "AlarmName": "Unhealthy instances for interactive-rendering in PROD", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "DatapointsToAlarm": 30, + "Dimensions": [ + { + "Name": "LoadBalancer", + "Value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "/", + { + "Ref": "ListenerInteractiverenderingB2E016A6", + }, + ], + }, + ], + }, + "/", + { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Ref": "ListenerInteractiverenderingB2E016A6", + }, + ], + }, + ], + }, + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Ref": "ListenerInteractiverenderingB2E016A6", + }, + ], + }, + ], + }, + ], + ], + }, + }, + { + "Name": "TargetGroup", + "Value": { + "Fn::GetAtt": [ + "TargetGroupInteractiverenderingB3F5F6D3", + "TargetGroupFullName", + ], + }, + }, + ], + "EvaluationPeriods": 60, + "MetricName": "UnHealthyHostCount", + "Namespace": "AWS/ApplicationELB", + "Period": 60, + "Statistic": "Maximum", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + "Threshold": 1, + "TreatMissingData": "notBreaching", + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "frontendPRODinteractiverenderingEABC55EF": { + "DependsOn": [ + "InstanceRoleInteractiverenderingA0F05C94", + ], + "Properties": { + "LaunchTemplateData": { + "IamInstanceProfile": { + "Arn": { + "Fn::GetAtt": [ + "frontendPRODinteractiverenderingProfileB2B85FC0", + "Arn", + ], + }, + }, + "ImageId": { + "Ref": "AMIInteractiverendering", + }, + "InstanceType": "c8g.medium", + "MetadataOptions": { + "HttpTokens": "required", + "InstanceMetadataTags": "enabled", + }, + "Monitoring": { + "Enabled": true, + }, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "GuHttpsEgressSecurityGroupInteractiverendering8AC409C4", + "GroupId", + ], + }, + ], + "TagSpecifications": [ + { + "ResourceType": "instance", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Name", + "Value": "InteractiveRendering-PROD/frontend-PROD-interactive-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + }, + { + "ResourceType": "volume", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Name", + "Value": "InteractiveRendering-PROD/frontend-PROD-interactive-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + }, + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash +set -ev +groupadd frontend +useradd -r -m -s /usr/bin/nologin -g frontend dotcom-rendering +cd /home/dotcom-rendering +aws --region eu-west-1 s3 cp s3://", + { + "Ref": "DistributionBucketName", + }, + "/frontend/PROD/interactive-rendering/interactive-rendering.tar.gz ./ +tar -zxf interactive-rendering.tar.gz interactive-rendering +chown -R dotcom-rendering:frontend interactive-rendering +cd interactive-rendering +mkdir /var/log/dotcom-rendering +chown -R dotcom-rendering:frontend /var/log/dotcom-rendering +cat > /etc/systemd/system/interactive-rendering.service << EOF +[Unit] +Description=interactive-rendering +After=network.target +[Service] +WorkingDirectory=/home/dotcom-rendering/interactive-rendering +Type=simple +User=dotcom-rendering +Group=frontend +StandardError=journal +StandardOutput=journal +Environment=TERM=xterm-256color +Environment=NODE_ENV=production +Environment=GU_STAGE=PROD +Environment=GU_APP=interactive-rendering +Environment=GU_STACK=frontend +ExecStart=make prod +Restart=on-failure +[Install] +WantedBy=multi-user.target +EOF +systemctl enable interactive-rendering +systemctl start interactive-rendering", + ], + ], + }, + }, + }, + "TagSpecifications": [ + { + "ResourceType": "launch-template", + "Tags": [ + { + "Key": "App", + "Value": "interactive-rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Name", + "Value": "InteractiveRendering-PROD/frontend-PROD-interactive-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "PROD", + }, + ], + }, + ], + }, + "Type": "AWS::EC2::LaunchTemplate", + }, + "frontendPRODinteractiverenderingProfileB2B85FC0": { + "Properties": { + "Roles": [ + { + "Ref": "InstanceRoleInteractiverenderingA0F05C94", + }, + ], + }, + "Type": "AWS::IAM::InstanceProfile", + }, + }, +} +`; + exports[`The RenderingCDKStack matches the snapshot for Tag Page Rendering CODE (uses ECS) 1`] = ` { "Metadata": { diff --git a/dotcom-rendering/cdk/lib/renderingStack.test.ts b/dotcom-rendering/cdk/lib/renderingStack.test.ts index 65680999fc6..d6c39cfc3a0 100644 --- a/dotcom-rendering/cdk/lib/renderingStack.test.ts +++ b/dotcom-rendering/cdk/lib/renderingStack.test.ts @@ -2,6 +2,8 @@ import { App } from 'aws-cdk-lib'; import { Template } from 'aws-cdk-lib/assertions'; import { InstanceClass, InstanceSize, InstanceType } from 'aws-cdk-lib/aws-ec2'; import { + InteractiveRenderingPropsCODE, + InteractiveRenderingPropsPROD, TagPageRenderingPropsCODE, TagPageRenderingPropsPROD, } from '../bin/cdk'; @@ -78,4 +80,27 @@ describe('The RenderingCDKStack', () => { const template = Template.fromStack(stack); expect(template.toJSON()).toMatchSnapshot(); }); + + it('matches the snapshot for Interactive Rendering PROD (not using ECS)', () => { + const app = new App(); + const stack = new RenderingCDKStack( + app, + 'InteractiveRendering-PROD', + InteractiveRenderingPropsPROD, + ); + const template = Template.fromStack(stack); + expect(template.toJSON()).toMatchSnapshot(); + }); + + it('matches the snapshot for Interactive Rendering CODE (uses ECS)', () => { + const app = new App(); + + const stack = new RenderingCDKStack( + app, + 'InteractiveRendering-CODE', + InteractiveRenderingPropsCODE, + ); + const template = Template.fromStack(stack); + expect(template.toJSON()).toMatchSnapshot(); + }); });