@guardian/cdk 61.5.2 → 61.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ import type { GuAsgCapacity } from "../../types";
8
8
  import type { AmigoProps } from "../../types/amigo";
9
9
  import { GuAmiParameter } from "../core";
10
10
  import type { AppIdentity, GuStack } from "../core";
11
+ export type InstanceMetricGranularity = "1Minute" | "5Minute";
11
12
  export interface GuAutoScalingGroupProps extends Omit<AutoScalingGroupProps, "imageId" | "osType" | "machineImage" | "userData" | "minCapacity" | "maxCapacity" | "desiredCapacity" | "requireImdsv2" | "securityGroup">, AppIdentity, GuAsgCapacity {
12
13
  /**
13
14
  * @deprecated
@@ -24,10 +25,19 @@ export interface GuAutoScalingGroupProps extends Omit<AutoScalingGroupProps, "im
24
25
  imageRecipe?: string | AmigoProps;
25
26
  instanceType: InstanceType;
26
27
  userData: UserData;
28
+ /**
29
+ * How often to send EC2 metrics, such as CPU usage.
30
+ * By default, AWS will produce `5Minute` granular metrics.
31
+ *
32
+ * It is recommended to produce `1Minute` granular metrics in production,
33
+ * especially when using ASG metrics to trigger horizontal scaling as it allows for earlier scaling.
34
+ *
35
+ * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html
36
+ */
37
+ instanceMetricGranularity: InstanceMetricGranularity;
27
38
  additionalSecurityGroups?: ISecurityGroup[];
28
39
  targetGroup?: ApplicationTargetGroup;
29
40
  httpPutResponseHopLimit?: number;
30
- enabledDetailedInstanceMonitoring?: boolean;
31
41
  defaultInstanceWarmup?: Duration;
32
42
  }
33
43
  declare const GuAutoScalingGroup_base: {
@@ -1 +1 @@
1
- {"version":3,"file":"asg.d.ts","sourceRoot":"","sources":["../../../src/constructs/autoscaling/asg.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAgB,MAAM,6BAA6B,CAAC;AAC7E,OAAO,KAAK,EAAE,qBAAqB,EAAuB,MAAM,6BAA6B,CAAC;AAC9F,OAAO,EAAE,cAAc,EAAuB,MAAM,qBAAqB,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAsB,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACtG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAQpD,MAAM,WAAW,uBACf,SAAQ,IAAI,CACR,qBAAqB,EACnB,SAAS,GACT,QAAQ,GACR,cAAc,GACd,UAAU,GACV,aAAa,GACb,aAAa,GACb,iBAAiB,GACjB,eAAe,GACf,eAAe,CAClB,EACD,WAAW,EACX,aAAa;IACf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAClC,YAAY,EAAE,YAAY,CAAC;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,wBAAwB,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5C,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,iCAAiC,CAAC,EAAE,OAAO,CAAC;IAC5C,qBAAqB,CAAC,EAAE,QAAQ,CAAC;CAClC;;;;;;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,kBAAmB,SAAQ,uBAAqC;IAC3E,SAAgB,GAAG,EAAE,MAAM,CAAC;IAC5B,SAAgB,YAAY,EAAE,cAAc,CAAC;IAC7C,SAAgB,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAGlD,SAAgB,sBAAsB,EAAE,cAAc,CAAC;gBAE3C,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,uBAAuB;CAsGvE"}
1
+ {"version":3,"file":"asg.d.ts","sourceRoot":"","sources":["../../../src/constructs/autoscaling/asg.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAgB,MAAM,6BAA6B,CAAC;AAC7E,OAAO,KAAK,EAAE,qBAAqB,EAAuB,MAAM,6BAA6B,CAAC;AAC9F,OAAO,EAAE,cAAc,EAAuB,MAAM,qBAAqB,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAsB,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACtG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAIpD,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,SAAS,CAAC;AAM9D,MAAM,WAAW,uBACf,SAAQ,IAAI,CACR,qBAAqB,EACnB,SAAS,GACT,QAAQ,GACR,cAAc,GACd,UAAU,GACV,aAAa,GACb,aAAa,GACb,iBAAiB,GACjB,eAAe,GACf,eAAe,CAClB,EACD,WAAW,EACX,aAAa;IACf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAClC,YAAY,EAAE,YAAY,CAAC;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IAEnB;;;;;;;;OAQG;IACH,yBAAyB,EAAE,yBAAyB,CAAC;IAErD,wBAAwB,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5C,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qBAAqB,CAAC,EAAE,QAAQ,CAAC;CAClC;;;;;;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,kBAAmB,SAAQ,uBAAqC;IAC3E,SAAgB,GAAG,EAAE,MAAM,CAAC;IAC5B,SAAgB,YAAY,EAAE,cAAc,CAAC;IAC7C,SAAgB,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAGlD,SAAgB,sBAAsB,EAAE,cAAc,CAAC;gBAE3C,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,uBAAuB;CAwGvE"}
@@ -29,16 +29,17 @@ const iam_1 = require("../iam");
29
29
  */
30
30
  class GuAutoScalingGroup extends (0, app_aware_construct_1.GuAppAwareConstruct)(aws_autoscaling_1.AutoScalingGroup) {
31
31
  constructor(scope, id, props) {
32
- const { app, additionalSecurityGroups = [], blockDevices, imageId = new core_1.GuAmiParameter(scope, { app }), imageRecipe, instanceType, groupMetrics = [aws_autoscaling_1.GroupMetrics.all()], minimumInstances, maximumInstances, role = new iam_1.GuInstanceRole(scope, { app }), targetGroup, userData, vpc, httpPutResponseHopLimit, updatePolicy, enabledDetailedInstanceMonitoring, defaultInstanceWarmup, } = props;
32
+ const { app, additionalSecurityGroups = [], blockDevices, imageId = new core_1.GuAmiParameter(scope, { app }), imageRecipe, instanceType, groupMetrics = [aws_autoscaling_1.GroupMetrics.all()], minimumInstances, maximumInstances, role = new iam_1.GuInstanceRole(scope, { app }), targetGroup, userData, vpc, httpPutResponseHopLimit, updatePolicy, defaultInstanceWarmup, instanceMetricGranularity, } = props;
33
33
  // Ensure min and max are defined in the same way. Throwing an `Error` when necessary. For example when min is defined via a Mapping, but max is not.
34
34
  if (aws_cdk_lib_1.Token.isUnresolved(minimumInstances) && !aws_cdk_lib_1.Token.isUnresolved(maximumInstances)) {
35
35
  throw new Error("minimumInstances is defined via a Mapping, but maximumInstances is not. Create maximumInstances via a Mapping too.");
36
36
  }
37
+ const detailedMonitoring = instanceMetricGranularity === "1Minute";
37
38
  // Generate an ID unique to this app
38
39
  const launchTemplateId = `${scope.stack}-${scope.stage}-${app}`;
39
40
  const launchTemplate = new aws_ec2_1.LaunchTemplate(scope, launchTemplateId, {
40
41
  blockDevices,
41
- detailedMonitoring: enabledDetailedInstanceMonitoring,
42
+ detailedMonitoring,
42
43
  instanceType,
43
44
  machineImage: {
44
45
  getImage: () => {
@@ -1 +1 @@
1
- {"version":3,"file":"asg.js","sourceRoot":"","sources":["../../../src/constructs/autoscaling/asg.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Duration } from \"aws-cdk-lib\";\nimport { Tags, Token } from \"aws-cdk-lib\";\nimport { AutoScalingGroup, GroupMetrics } from \"aws-cdk-lib/aws-autoscaling\";\nimport type { AutoScalingGroupProps, CfnAutoScalingGroup } from \"aws-cdk-lib/aws-autoscaling\";\nimport { LaunchTemplate, OperatingSystemType } from \"aws-cdk-lib/aws-ec2\";\nimport type { InstanceType, ISecurityGroup, MachineImageConfig, UserData } from \"aws-cdk-lib/aws-ec2\";\nimport type { ApplicationTargetGroup } from \"aws-cdk-lib/aws-elasticloadbalancingv2\";\nimport type { GuAsgCapacity } from \"../../types\";\nimport type { AmigoProps } from \"../../types/amigo\";\nimport { GuAppAwareConstruct } from \"../../utils/mixin/app-aware-construct\";\nimport { GuAmiParameter } from \"../core\";\nimport type { AppIdentity, GuStack } from \"../core\";\nimport { GuHttpsEgressSecurityGroup } from \"../ec2\";\nimport { GuInstanceRole } from \"../iam\";\n\n// Since we want to override the types of what gets passed in for the below props,\n// we need to use Omit<T, U> to remove them from the interface this extends\n// https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys\n// TODO avoid prop nesting to improve discoverability. See https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md#flat.\nexport interface GuAutoScalingGroupProps\n extends Omit<\n AutoScalingGroupProps,\n | \"imageId\"\n | \"osType\"\n | \"machineImage\"\n | \"userData\"\n | \"minCapacity\"\n | \"maxCapacity\"\n | \"desiredCapacity\"\n | \"requireImdsv2\"\n | \"securityGroup\"\n >,\n AppIdentity,\n GuAsgCapacity {\n /**\n * @deprecated\n * It shouldn't be necessary to specify a value here.\n * GuCDK will create an AMI parameter by default. Riff-Raff can use this parameter to inject the latest AMI ID for\n * your AMIgo recipe.\n */\n imageId?: GuAmiParameter;\n /**\n * If you are using GuCDK to generate your riff-raff.yaml file, specify AMIgo props here.\n * If you are using a hardcoded riff-raff.yaml file (usually found in project root) then providing a value here has no\n * effect.\n */\n imageRecipe?: string | AmigoProps;\n instanceType: InstanceType;\n userData: UserData;\n additionalSecurityGroups?: ISecurityGroup[];\n targetGroup?: ApplicationTargetGroup;\n httpPutResponseHopLimit?: number;\n enabledDetailedInstanceMonitoring?: boolean;\n defaultInstanceWarmup?: Duration;\n}\n\n/**\n * Construct which creates an Auto Scaling group.\n *\n * By default, all EC2 instances in this group will use [[`GuInstanceRole`]],\n * which provides common permissions (e.g. the ability to download an artifact and write logs to the account's logging Kinesis stream).\n *\n * If additional IAM permissions are required, a custom role can be provided via the `role` prop.\n * You may wish to instantiate [[`GuInstanceRole`]] yourself as a basis for this custom role, as it allows custom permissions\n * to be passed in.\n *\n * All EC2 instances in this group will be automatically associated with the [[`GuHttpsEgressSecurityGroup`]] security groups,\n * which allows outbound traffic over HTTPS.\n *\n * If additional ingress or egress rules are required, define custom security groups and pass them in via the\n * `additionalSecurityGroups` prop.\n *\n * All EC2 instances provisioned via this construct will use\n * [IMDSv2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html).\n */\nexport class GuAutoScalingGroup extends GuAppAwareConstruct(AutoScalingGroup) {\n public readonly app: string;\n public readonly amiParameter: GuAmiParameter;\n public readonly imageRecipe?: string | AmigoProps;\n\n // Sadly this cannot be named `launchTemplate` as it would clash with a private property on the `AutoScalingGroup` class\n public readonly instanceLaunchTemplate: LaunchTemplate;\n\n constructor(scope: GuStack, id: string, props: GuAutoScalingGroupProps) {\n const {\n app,\n additionalSecurityGroups = [],\n blockDevices,\n imageId = new GuAmiParameter(scope, { app }),\n imageRecipe,\n instanceType,\n groupMetrics = [GroupMetrics.all()],\n minimumInstances,\n maximumInstances,\n role = new GuInstanceRole(scope, { app }),\n targetGroup,\n userData,\n vpc,\n httpPutResponseHopLimit,\n updatePolicy,\n enabledDetailedInstanceMonitoring,\n defaultInstanceWarmup,\n } = props;\n\n // Ensure min and max are defined in the same way. Throwing an `Error` when necessary. For example when min is defined via a Mapping, but max is not.\n if (Token.isUnresolved(minimumInstances) && !Token.isUnresolved(maximumInstances)) {\n throw new Error(\n \"minimumInstances is defined via a Mapping, but maximumInstances is not. Create maximumInstances via a Mapping too.\",\n );\n }\n\n // Generate an ID unique to this app\n const launchTemplateId = `${scope.stack}-${scope.stage}-${app}`;\n const launchTemplate = new LaunchTemplate(scope, launchTemplateId, {\n blockDevices,\n detailedMonitoring: enabledDetailedInstanceMonitoring,\n instanceType,\n machineImage: {\n getImage: (): MachineImageConfig => {\n return {\n osType: OperatingSystemType.LINUX,\n userData,\n imageId: imageId.valueAsString,\n };\n },\n },\n // Do not use the default AWS security group which allows egress on any port.\n // Favour HTTPS only egress rules by default.\n securityGroup: GuHttpsEgressSecurityGroup.forVpc(scope, { app, vpc }),\n\n /*\n Ensure we satisfy FSBP EC2.8 control. If needed, an escape hatch can override this.\n See https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-8.\n */\n requireImdsv2: true,\n\n instanceMetadataTags: true,\n userData,\n role,\n httpPutResponseHopLimit,\n });\n\n // Add additional consumer specified Security Groups\n // Note: Launch templates via CDK allow specifying only one SG, so use connections\n // https://github.com/aws/aws-cdk/issues/18712\n additionalSecurityGroups.forEach((sg) => launchTemplate.connections.addSecurityGroup(sg));\n\n const asgProps: AutoScalingGroupProps = {\n ...props,\n launchTemplate,\n maxCapacity: maximumInstances ?? minimumInstances * 2,\n minCapacity: minimumInstances,\n groupMetrics: groupMetrics,\n\n // Omit userData, instanceType, blockDevices & role from asgProps\n // As this are specified by the LaunchTemplate and must not be duplicated\n blockDevices: undefined,\n instanceType: undefined,\n role: undefined,\n userData: undefined,\n defaultInstanceWarmup,\n };\n\n super(scope, id, asgProps);\n\n this.app = app;\n this.amiParameter = imageId;\n this.imageRecipe = imageRecipe;\n this.instanceLaunchTemplate = launchTemplate;\n\n if (targetGroup) {\n this.attachToApplicationTargetGroup(targetGroup);\n }\n\n const cfnAsg = this.node.defaultChild as CfnAutoScalingGroup;\n\n // A CDK AutoScalingGroup comes with this update policy, whereas the CFN autscaling group\n // leaves it to the default value, which is actually false.\n // { UpdatePolicy: { autoScalingScheduledAction: { IgnoreUnmodifiedGroupSizeProperties: true }}\n if (!updatePolicy) {\n cfnAsg.addDeletionOverride(\"UpdatePolicy\");\n }\n\n Tags.of(launchTemplate).add(\"App\", app);\n }\n}\n"]}
1
+ {"version":3,"file":"asg.js","sourceRoot":"","sources":["../../../src/constructs/autoscaling/asg.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Duration } from \"aws-cdk-lib\";\nimport { Tags, Token } from \"aws-cdk-lib\";\nimport { AutoScalingGroup, GroupMetrics } from \"aws-cdk-lib/aws-autoscaling\";\nimport type { AutoScalingGroupProps, CfnAutoScalingGroup } from \"aws-cdk-lib/aws-autoscaling\";\nimport { LaunchTemplate, OperatingSystemType } from \"aws-cdk-lib/aws-ec2\";\nimport type { InstanceType, ISecurityGroup, MachineImageConfig, UserData } from \"aws-cdk-lib/aws-ec2\";\nimport type { ApplicationTargetGroup } from \"aws-cdk-lib/aws-elasticloadbalancingv2\";\nimport type { GuAsgCapacity } from \"../../types\";\nimport type { AmigoProps } from \"../../types/amigo\";\nimport { GuAppAwareConstruct } from \"../../utils/mixin/app-aware-construct\";\nimport { GuAmiParameter } from \"../core\";\nimport type { AppIdentity, GuStack } from \"../core\";\nimport { GuHttpsEgressSecurityGroup } from \"../ec2\";\nimport { GuInstanceRole } from \"../iam\";\n\nexport type InstanceMetricGranularity = \"1Minute\" | \"5Minute\";\n\n// Since we want to override the types of what gets passed in for the below props,\n// we need to use Omit<T, U> to remove them from the interface this extends\n// https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys\n// TODO avoid prop nesting to improve discoverability. See https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md#flat.\nexport interface GuAutoScalingGroupProps\n extends Omit<\n AutoScalingGroupProps,\n | \"imageId\"\n | \"osType\"\n | \"machineImage\"\n | \"userData\"\n | \"minCapacity\"\n | \"maxCapacity\"\n | \"desiredCapacity\"\n | \"requireImdsv2\"\n | \"securityGroup\"\n >,\n AppIdentity,\n GuAsgCapacity {\n /**\n * @deprecated\n * It shouldn't be necessary to specify a value here.\n * GuCDK will create an AMI parameter by default. Riff-Raff can use this parameter to inject the latest AMI ID for\n * your AMIgo recipe.\n */\n imageId?: GuAmiParameter;\n /**\n * If you are using GuCDK to generate your riff-raff.yaml file, specify AMIgo props here.\n * If you are using a hardcoded riff-raff.yaml file (usually found in project root) then providing a value here has no\n * effect.\n */\n imageRecipe?: string | AmigoProps;\n instanceType: InstanceType;\n userData: UserData;\n\n /**\n * How often to send EC2 metrics, such as CPU usage.\n * By default, AWS will produce `5Minute` granular metrics.\n *\n * It is recommended to produce `1Minute` granular metrics in production,\n * especially when using ASG metrics to trigger horizontal scaling as it allows for earlier scaling.\n *\n * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html\n */\n instanceMetricGranularity: InstanceMetricGranularity;\n\n additionalSecurityGroups?: ISecurityGroup[];\n targetGroup?: ApplicationTargetGroup;\n httpPutResponseHopLimit?: number;\n defaultInstanceWarmup?: Duration;\n}\n\n/**\n * Construct which creates an Auto Scaling group.\n *\n * By default, all EC2 instances in this group will use [[`GuInstanceRole`]],\n * which provides common permissions (e.g. the ability to download an artifact and write logs to the account's logging Kinesis stream).\n *\n * If additional IAM permissions are required, a custom role can be provided via the `role` prop.\n * You may wish to instantiate [[`GuInstanceRole`]] yourself as a basis for this custom role, as it allows custom permissions\n * to be passed in.\n *\n * All EC2 instances in this group will be automatically associated with the [[`GuHttpsEgressSecurityGroup`]] security groups,\n * which allows outbound traffic over HTTPS.\n *\n * If additional ingress or egress rules are required, define custom security groups and pass them in via the\n * `additionalSecurityGroups` prop.\n *\n * All EC2 instances provisioned via this construct will use\n * [IMDSv2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html).\n */\nexport class GuAutoScalingGroup extends GuAppAwareConstruct(AutoScalingGroup) {\n public readonly app: string;\n public readonly amiParameter: GuAmiParameter;\n public readonly imageRecipe?: string | AmigoProps;\n\n // Sadly this cannot be named `launchTemplate` as it would clash with a private property on the `AutoScalingGroup` class\n public readonly instanceLaunchTemplate: LaunchTemplate;\n\n constructor(scope: GuStack, id: string, props: GuAutoScalingGroupProps) {\n const {\n app,\n additionalSecurityGroups = [],\n blockDevices,\n imageId = new GuAmiParameter(scope, { app }),\n imageRecipe,\n instanceType,\n groupMetrics = [GroupMetrics.all()],\n minimumInstances,\n maximumInstances,\n role = new GuInstanceRole(scope, { app }),\n targetGroup,\n userData,\n vpc,\n httpPutResponseHopLimit,\n updatePolicy,\n defaultInstanceWarmup,\n instanceMetricGranularity,\n } = props;\n\n // Ensure min and max are defined in the same way. Throwing an `Error` when necessary. For example when min is defined via a Mapping, but max is not.\n if (Token.isUnresolved(minimumInstances) && !Token.isUnresolved(maximumInstances)) {\n throw new Error(\n \"minimumInstances is defined via a Mapping, but maximumInstances is not. Create maximumInstances via a Mapping too.\",\n );\n }\n\n const detailedMonitoring = instanceMetricGranularity === \"1Minute\";\n\n // Generate an ID unique to this app\n const launchTemplateId = `${scope.stack}-${scope.stage}-${app}`;\n const launchTemplate = new LaunchTemplate(scope, launchTemplateId, {\n blockDevices,\n detailedMonitoring,\n instanceType,\n machineImage: {\n getImage: (): MachineImageConfig => {\n return {\n osType: OperatingSystemType.LINUX,\n userData,\n imageId: imageId.valueAsString,\n };\n },\n },\n // Do not use the default AWS security group which allows egress on any port.\n // Favour HTTPS only egress rules by default.\n securityGroup: GuHttpsEgressSecurityGroup.forVpc(scope, { app, vpc }),\n\n /*\n Ensure we satisfy FSBP EC2.8 control. If needed, an escape hatch can override this.\n See https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-8.\n */\n requireImdsv2: true,\n\n instanceMetadataTags: true,\n userData,\n role,\n httpPutResponseHopLimit,\n });\n\n // Add additional consumer specified Security Groups\n // Note: Launch templates via CDK allow specifying only one SG, so use connections\n // https://github.com/aws/aws-cdk/issues/18712\n additionalSecurityGroups.forEach((sg) => launchTemplate.connections.addSecurityGroup(sg));\n\n const asgProps: AutoScalingGroupProps = {\n ...props,\n launchTemplate,\n maxCapacity: maximumInstances ?? minimumInstances * 2,\n minCapacity: minimumInstances,\n groupMetrics: groupMetrics,\n\n // Omit userData, instanceType, blockDevices & role from asgProps\n // As this are specified by the LaunchTemplate and must not be duplicated\n blockDevices: undefined,\n instanceType: undefined,\n role: undefined,\n userData: undefined,\n defaultInstanceWarmup,\n };\n\n super(scope, id, asgProps);\n\n this.app = app;\n this.amiParameter = imageId;\n this.imageRecipe = imageRecipe;\n this.instanceLaunchTemplate = launchTemplate;\n\n if (targetGroup) {\n this.attachToApplicationTargetGroup(targetGroup);\n }\n\n const cfnAsg = this.node.defaultChild as CfnAutoScalingGroup;\n\n // A CDK AutoScalingGroup comes with this update policy, whereas the CFN autscaling group\n // leaves it to the default value, which is actually false.\n // { UpdatePolicy: { autoScalingScheduledAction: { IgnoreUnmodifiedGroupSizeProperties: true }}\n if (!updatePolicy) {\n cfnAsg.addDeletionOverride(\"UpdatePolicy\");\n }\n\n Tags.of(launchTemplate).add(\"App\", app);\n }\n}\n"]}
@@ -1,8 +1,10 @@
1
1
  import type { IAspect } from "aws-cdk-lib";
2
2
  import { CfnParameter, Duration } from "aws-cdk-lib";
3
+ import { UpdatePolicy } from "aws-cdk-lib/aws-autoscaling";
3
4
  import type { IConstruct } from "constructs";
4
5
  import { GuAutoScalingGroup } from "../../constructs/autoscaling";
5
6
  import type { GuStack } from "../../constructs/core";
7
+ import type { GuApplicationTargetGroup } from "../../constructs/loadbalancing";
6
8
  import type { GuEc2AppProps } from "../../patterns";
7
9
  import { GuEc2App } from "../../patterns";
8
10
  interface AutoScalingRollingUpdateDurations {
@@ -16,6 +18,26 @@ interface AutoScalingRollingUpdateDurations {
16
18
  buffer: Duration;
17
19
  }
18
20
  export declare const RollingUpdateDurations: AutoScalingRollingUpdateDurations;
21
+ /**
22
+ * Ensures the `AutoScalingRollingUpdate` of an AutoScaling Group has a `PauseTime` matching the healthcheck grace period.
23
+ * It also ensures the `CreationPolicy` resource signal `Timeout` matches the healthcheck grace period.
24
+ *
25
+ * @internal
26
+ *
27
+ * @privateRemarks
28
+ * The ASG healthcheck grace period is hard-coded by {@link GuEc2App}.
29
+ * Customisation of this value is performed via an escape hatch.
30
+ * An `Aspect` is the only way to observe any customisation.
31
+ *
32
+ * TODO Expose the healthcheck grace period as a property on {@link GuEc2App} and remove this `Aspect`.
33
+ */
34
+ export declare class GuAutoScalingRollingUpdateTimeoutExperimental implements IAspect {
35
+ readonly stack: GuStack;
36
+ private static instance;
37
+ private constructor();
38
+ static getInstance(stack: GuStack): GuAutoScalingRollingUpdateTimeoutExperimental;
39
+ visit(construct: IConstruct): void;
40
+ }
19
41
  /**
20
42
  * An `Aspect` that adjusts the properties of an AutoScaling Group using an `AutoScalingRollingUpdate` update policy.
21
43
  *
@@ -36,12 +58,12 @@ export declare const RollingUpdateDurations: AutoScalingRollingUpdateDurations;
36
58
  *
37
59
  * @see https://github.com/guardian/testing-asg-rolling-update
38
60
  */
39
- export declare class HorizontallyScalingDeploymentProperties implements IAspect {
61
+ export declare class GuHorizontallyScalingDeploymentPropertiesExperimental implements IAspect {
40
62
  readonly stack: GuStack;
41
63
  readonly asgToParamMap: Map<string, CfnParameter>;
42
64
  private static instance;
43
65
  private constructor();
44
- static getInstance(stack: GuStack): HorizontallyScalingDeploymentProperties;
66
+ static getInstance(stack: GuStack): GuHorizontallyScalingDeploymentPropertiesExperimental;
45
67
  visit(construct: IConstruct): void;
46
68
  }
47
69
  export declare function getAsgRollingUpdateCfnParameterName(autoScalingGroup: GuAutoScalingGroup): string;
@@ -55,6 +77,49 @@ export interface GuEc2AppExperimentalProps extends Omit<GuEc2AppProps, "updatePo
55
77
  */
56
78
  buildIdentifier: string;
57
79
  }
80
+ /**
81
+ * Configures the rolling update policy for the autoscaling group.
82
+ *
83
+ * Most applications should not need to instantiate this class directly as this policy is set by GuEc2AppExperimental.
84
+ *
85
+ * If you need to use this class for a service other than MAPI, please speak to DevX about your use-case first.
86
+ */
87
+ export declare class GuRollingUpdatePolicyExperimental {
88
+ static getPolicy(maximumInstances: number, minimumInstances?: number): UpdatePolicy;
89
+ }
90
+ export interface GuRoleForRollingUpdateExperimentalProps {
91
+ autoScalingGroup: GuAutoScalingGroup;
92
+ }
93
+ /**
94
+ * Grants the role associated with your autoscaling group the necessary permissions for the rolling update deployment
95
+ * mechanism.
96
+ *
97
+ * Most applications should not need to instantiate this class directly as these permission changes are granted
98
+ * by GuEc2AppExperimental.
99
+ *
100
+ * If you need to use this class for a service other than MAPI, please speak to DevX about your use-case first.
101
+ */
102
+ export declare class GuRoleForRollingUpdateExperimental {
103
+ constructor(scope: GuStack, props: GuRoleForRollingUpdateExperimentalProps);
104
+ }
105
+ export interface GuUserDataForRollingUpdateExperimentalProps {
106
+ autoScalingGroup: GuAutoScalingGroup;
107
+ targetGroup: GuApplicationTargetGroup;
108
+ applicationPort: number;
109
+ buildIdentifier: string;
110
+ }
111
+ /**
112
+ * Modifies the user-data script (which runs whenever an instance is launched) to make it compatible with the new
113
+ * rolling update deployment mechanism.
114
+ *
115
+ * Most applications should not need to instantiate this class directly as these user-data changes are already made
116
+ * by GuEc2AppExperimental.
117
+ *
118
+ * If you need to use this class for a service other than MAPI, please speak to DevX about your use-case first.
119
+ */
120
+ export declare class GuUserDataForRollingUpdateExperimental {
121
+ constructor(scope: GuStack, props: GuUserDataForRollingUpdateExperimentalProps);
122
+ }
58
123
  /**
59
124
  * An experimental pattern to instantiate an EC2 application that is updated entirely via CloudFormation.
60
125
  * It sets the update policy of the `AWS::AutoScaling::AutoScalingGroup` to `AutoScalingRollingUpdate`.
@@ -1 +1 @@
1
- {"version":3,"file":"ec2-app.d.ts","sourceRoot":"","sources":["../../../src/experimental/patterns/ec2-app.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAW,YAAY,EAAE,QAAQ,EAAQ,MAAM,aAAa,CAAC;AAIpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,UAAU,iCAAiC;IACzC;;OAEG;IACH,KAAK,EAAE,QAAQ,CAAC;IAEhB;;OAEG;IACH,MAAM,EAAE,QAAQ,CAAC;CAClB;AAED,eAAO,MAAM,sBAAsB,EAAE,iCAGpC,CAAC;AA+DF;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,qBAAa,uCAAwC,YAAW,OAAO;IACrE,SAAgB,KAAK,EAAE,OAAO,CAAC;IAC/B,SAAgB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAsD;IAE7E,OAAO;WAKO,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,uCAAuC;IAQ3E,KAAK,CAAC,SAAS,EAAE,UAAU;CA+DnC;AAED,wBAAgB,mCAAmC,CAAC,gBAAgB,EAAE,kBAAkB,UAGvF;AA8CD,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC;IACpF;;;;;;OAMG;IACH,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,oBAAqB,SAAQ,QAAQ;gBACpC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,yBAAyB;CA+F7D"}
1
+ {"version":3,"file":"ec2-app.d.ts","sourceRoot":"","sources":["../../../src/experimental/patterns/ec2-app.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAW,YAAY,EAAE,QAAQ,EAAQ,MAAM,aAAa,CAAC;AACpE,OAAO,EAAyD,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGlH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,UAAU,iCAAiC;IACzC;;OAEG;IACH,KAAK,EAAE,QAAQ,CAAC;IAEhB;;OAEG;IACH,MAAM,EAAE,QAAQ,CAAC;CAClB;AAED,eAAO,MAAM,sBAAsB,EAAE,iCAGpC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,qBAAa,6CAA8C,YAAW,OAAO;IAC3E,SAAgB,KAAK,EAAE,OAAO,CAAC;IAC/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA4D;IAEnF,OAAO;WAIO,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,6CAA6C;IAOjF,KAAK,CAAC,SAAS,EAAE,UAAU;CA+BnC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,qDAAsD,YAAW,OAAO;IACnF,SAAgB,KAAK,EAAE,OAAO,CAAC;IAC/B,SAAgB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAoE;IAE3F,OAAO;WAKO,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,qDAAqD;IAQzF,KAAK,CAAC,SAAS,EAAE,UAAU;CA+DnC;AAED,wBAAgB,mCAAmC,CAAC,gBAAgB,EAAE,kBAAkB,UAGvF;AA8CD,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC;IACpF;;;;;;OAMG;IACH,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,qBAAa,iCAAiC;IAC5C,MAAM,CAAC,SAAS,CAAC,gBAAgB,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM;CAgBrE;AAED,MAAM,WAAW,uCAAuC;IACtD,gBAAgB,EAAE,kBAAkB,CAAC;CACtC;AAED;;;;;;;;GAQG;AACH,qBAAa,kCAAkC;gBACjC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,uCAAuC;CAU3E;AAED,MAAM,WAAW,2CAA2C;IAC1D,gBAAgB,EAAE,kBAAkB,CAAC;IACrC,WAAW,EAAE,wBAAwB,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,qBAAa,sCAAsC;gBACrC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,2CAA2C;CAkD/E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,oBAAqB,SAAQ,QAAQ;gBACpC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,yBAAyB;CAsC7D"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GuEc2AppExperimental = exports.getAsgRollingUpdateCfnParameterName = exports.HorizontallyScalingDeploymentProperties = exports.RollingUpdateDurations = void 0;
3
+ exports.GuEc2AppExperimental = exports.GuUserDataForRollingUpdateExperimental = exports.GuRoleForRollingUpdateExperimental = exports.GuRollingUpdatePolicyExperimental = exports.getAsgRollingUpdateCfnParameterName = exports.GuHorizontallyScalingDeploymentPropertiesExperimental = exports.GuAutoScalingRollingUpdateTimeoutExperimental = exports.RollingUpdateDurations = void 0;
4
4
  const aws_cdk_lib_1 = require("aws-cdk-lib");
5
5
  const aws_autoscaling_1 = require("aws-cdk-lib/aws-autoscaling");
6
6
  const aws_iam_1 = require("aws-cdk-lib/aws-iam");
@@ -25,13 +25,13 @@ exports.RollingUpdateDurations = {
25
25
  *
26
26
  * TODO Expose the healthcheck grace period as a property on {@link GuEc2App} and remove this `Aspect`.
27
27
  */
28
- class AutoScalingRollingUpdateTimeout {
28
+ class GuAutoScalingRollingUpdateTimeoutExperimental {
29
29
  constructor(scope) {
30
30
  this.stack = scope;
31
31
  }
32
32
  static getInstance(stack) {
33
33
  if (!this.instance || !(0, singleton_1.isSingletonPresentInStack)(stack, this.instance)) {
34
- this.instance = new AutoScalingRollingUpdateTimeout(stack);
34
+ this.instance = new GuAutoScalingRollingUpdateTimeoutExperimental(stack);
35
35
  }
36
36
  return this.instance;
37
37
  }
@@ -63,6 +63,7 @@ class AutoScalingRollingUpdateTimeout {
63
63
  }
64
64
  }
65
65
  }
66
+ exports.GuAutoScalingRollingUpdateTimeoutExperimental = GuAutoScalingRollingUpdateTimeoutExperimental;
66
67
  /**
67
68
  * An `Aspect` that adjusts the properties of an AutoScaling Group using an `AutoScalingRollingUpdate` update policy.
68
69
  *
@@ -83,15 +84,14 @@ class AutoScalingRollingUpdateTimeout {
83
84
  *
84
85
  * @see https://github.com/guardian/testing-asg-rolling-update
85
86
  */
86
- // eslint-disable-next-line custom-rules/experimental-classes -- this class is not indented for public use
87
- class HorizontallyScalingDeploymentProperties {
87
+ class GuHorizontallyScalingDeploymentPropertiesExperimental {
88
88
  constructor(scope) {
89
89
  this.stack = scope;
90
90
  this.asgToParamMap = new Map();
91
91
  }
92
92
  static getInstance(stack) {
93
93
  if (!this.instance || !(0, singleton_1.isSingletonPresentInStack)(stack, this.instance)) {
94
- this.instance = new HorizontallyScalingDeploymentProperties(stack);
94
+ this.instance = new GuHorizontallyScalingDeploymentPropertiesExperimental(stack);
95
95
  }
96
96
  return this.instance;
97
97
  }
@@ -146,7 +146,7 @@ class HorizontallyScalingDeploymentProperties {
146
146
  }
147
147
  }
148
148
  }
149
- exports.HorizontallyScalingDeploymentProperties = HorizontallyScalingDeploymentProperties;
149
+ exports.GuHorizontallyScalingDeploymentPropertiesExperimental = GuHorizontallyScalingDeploymentPropertiesExperimental;
150
150
  function getAsgRollingUpdateCfnParameterName(autoScalingGroup) {
151
151
  const { app } = autoScalingGroup;
152
152
  return `MinInstancesInServiceFor${app.replaceAll("-", "")}`;
@@ -189,6 +189,106 @@ class AsgRollingUpdatePolicy extends aws_iam_1.Policy {
189
189
  return this.instance;
190
190
  }
191
191
  }
192
+ /**
193
+ * Configures the rolling update policy for the autoscaling group.
194
+ *
195
+ * Most applications should not need to instantiate this class directly as this policy is set by GuEc2AppExperimental.
196
+ *
197
+ * If you need to use this class for a service other than MAPI, please speak to DevX about your use-case first.
198
+ */
199
+ class GuRollingUpdatePolicyExperimental {
200
+ static getPolicy(maximumInstances, minimumInstances) {
201
+ return aws_autoscaling_1.UpdatePolicy.rollingUpdate({
202
+ maxBatchSize: maximumInstances,
203
+ minInstancesInService: minimumInstances,
204
+ minSuccessPercentage: 100,
205
+ waitOnResourceSignals: true,
206
+ /*
207
+ If a scale-in event fires during an `AutoScalingRollingUpdate` operation, the update could fail and rollback.
208
+ For this reason, we suspend the `AlarmNotification` process, else availability of a service cannot be guaranteed.
209
+ Consequently, services cannot scale-out during deployments.
210
+ If AWS ever supports suspending scale-out and scale-in independently, we should allow scale-out.
211
+ */
212
+ suspendProcesses: [aws_autoscaling_1.ScalingProcess.ALARM_NOTIFICATION],
213
+ // Note: there is also an important property called pauseTime, but this is set via an Aspect.
214
+ });
215
+ }
216
+ }
217
+ exports.GuRollingUpdatePolicyExperimental = GuRollingUpdatePolicyExperimental;
218
+ /**
219
+ * Grants the role associated with your autoscaling group the necessary permissions for the rolling update deployment
220
+ * mechanism.
221
+ *
222
+ * Most applications should not need to instantiate this class directly as these permission changes are granted
223
+ * by GuEc2AppExperimental.
224
+ *
225
+ * If you need to use this class for a service other than MAPI, please speak to DevX about your use-case first.
226
+ */
227
+ class GuRoleForRollingUpdateExperimental {
228
+ constructor(scope, props) {
229
+ const policy = AsgRollingUpdatePolicy.getInstance(scope);
230
+ policy.attachToRole(props.autoScalingGroup.role);
231
+ // Create the Policy with necessary permissions first.
232
+ // Then create the ASG that requires the permissions.
233
+ const cfnPolicy = policy.node.defaultChild;
234
+ const cfnAutoScalingGroup = props.autoScalingGroup.node.defaultChild;
235
+ cfnAutoScalingGroup.addDependency(cfnPolicy);
236
+ }
237
+ }
238
+ exports.GuRoleForRollingUpdateExperimental = GuRoleForRollingUpdateExperimental;
239
+ /**
240
+ * Modifies the user-data script (which runs whenever an instance is launched) to make it compatible with the new
241
+ * rolling update deployment mechanism.
242
+ *
243
+ * Most applications should not need to instantiate this class directly as these user-data changes are already made
244
+ * by GuEc2AppExperimental.
245
+ *
246
+ * If you need to use this class for a service other than MAPI, please speak to DevX about your use-case first.
247
+ */
248
+ class GuUserDataForRollingUpdateExperimental {
249
+ constructor(scope, props) {
250
+ const { region, stackId } = scope;
251
+ const { autoScalingGroup, targetGroup, applicationPort, buildIdentifier } = props;
252
+ const cfnAutoScalingGroup = autoScalingGroup.node.defaultChild;
253
+ /*
254
+ `aws` is available via AMIgo baked AMIs.
255
+ See https://github.com/guardian/amigo/tree/main/roles/aws-tools.
256
+ */
257
+ autoScalingGroup.userData.addCommands(`# ${GuEc2AppExperimental.name} UserData Start`, `
258
+ TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
259
+ INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" "http://169.254.169.254/latest/meta-data/instance-id")
260
+
261
+ STATE=$(aws elbv2 describe-target-health \
262
+ --target-group-arn ${targetGroup.targetGroupArn} \
263
+ --region ${region} \
264
+ --targets Id=$INSTANCE_ID,Port=${applicationPort} \
265
+ --query "TargetHealthDescriptions[0].TargetHealth.State")
266
+
267
+ until [ "$STATE" == "\\"healthy\\"" ]; do
268
+ echo "Instance running build ${buildIdentifier} not yet healthy within target group. Current state $STATE. Sleeping..."
269
+ sleep ${exports.RollingUpdateDurations.sleep.toSeconds()}
270
+ STATE=$(aws elbv2 describe-target-health \
271
+ --target-group-arn ${targetGroup.targetGroupArn} \
272
+ --region ${region} \
273
+ --targets Id=$INSTANCE_ID,Port=${applicationPort} \
274
+ --query "TargetHealthDescriptions[0].TargetHealth.State")
275
+ done
276
+
277
+ echo "Instance running build ${buildIdentifier} is healthy in target group."
278
+ `, `# ${GuEc2AppExperimental.name} UserData End`);
279
+ autoScalingGroup.userData.addOnExitCommands(`
280
+ cfn-signal --stack ${stackId} \
281
+ --resource ${cfnAutoScalingGroup.logicalId} \
282
+ --region ${region} \
283
+ --exit-code $exitCode || echo 'Failed to send Cloudformation Signal'
284
+ `);
285
+ // If https://github.com/guardian/devx-logs is used, this tag will be added as a marker to logs in Central ELK.
286
+ aws_cdk_lib_1.Tags.of(autoScalingGroup.instanceLaunchTemplate).add(constants_1.MetadataKeys.BUILD_IDENTIFIER, buildIdentifier, {
287
+ applyToLaunchedInstances: true,
288
+ });
289
+ }
290
+ }
291
+ exports.GuUserDataForRollingUpdateExperimental = GuUserDataForRollingUpdateExperimental;
192
292
  /**
193
293
  * An experimental pattern to instantiate an EC2 application that is updated entirely via CloudFormation.
194
294
  * It sets the update policy of the `AWS::AutoScaling::AutoScalingGroup` to `AutoScalingRollingUpdate`.
@@ -227,27 +327,16 @@ class GuEc2AppExperimental extends patterns_1.GuEc2App {
227
327
  constructor(scope, props) {
228
328
  const { minimumInstances, maximumInstances = minimumInstances * 2 } = props.scaling;
229
329
  const { applicationPort, buildIdentifier } = props;
230
- const { region, stackId } = scope;
231
330
  super(scope, {
232
331
  ...props,
233
- updatePolicy: aws_autoscaling_1.UpdatePolicy.rollingUpdate({
234
- maxBatchSize: maximumInstances,
235
- minInstancesInService: minimumInstances,
236
- minSuccessPercentage: 100,
237
- waitOnResourceSignals: true,
238
- /*
239
- If a scale-in event fires during an `AutoScalingRollingUpdate` operation, the update could fail and rollback.
240
- For this reason, we suspend the `AlarmNotification` process, else availability of a service cannot be guaranteed.
241
- Consequently, services cannot scale-out during deployments.
242
- If AWS ever supports suspending scale-out and scale-in independently, we should allow scale-out.
243
- */
244
- suspendProcesses: [aws_autoscaling_1.ScalingProcess.ALARM_NOTIFICATION],
245
- }),
332
+ // Note: if the service uses horizontal scaling, minimumInstances is overridden by an Aspect (to prevent accidental scale downs!)
333
+ updatePolicy: GuRollingUpdatePolicyExperimental.getPolicy(maximumInstances, minimumInstances),
246
334
  });
247
335
  const { autoScalingGroup, targetGroup } = this;
248
- const { userData, role } = autoScalingGroup;
249
336
  const cfnAutoScalingGroup = autoScalingGroup.node.defaultChild;
337
+ // Note: if the service uses horizontal scaling, this property is unset by an Aspect (to prevent accidental scale downs!)
250
338
  cfnAutoScalingGroup.desiredCapacity = minimumInstances.toString();
339
+ // This is used when an ASG is first created; it is not needed to support the new deployment mechanism
251
340
  cfnAutoScalingGroup.cfnOptions.creationPolicy = {
252
341
  autoScalingCreationPolicy: {
253
342
  minSuccessfulInstancesPercent: 100,
@@ -256,51 +345,16 @@ class GuEc2AppExperimental extends patterns_1.GuEc2App {
256
345
  count: minimumInstances,
257
346
  },
258
347
  };
259
- const policy = AsgRollingUpdatePolicy.getInstance(scope);
260
- policy.attachToRole(role);
261
- // Create the Policy with necessary permissions first.
262
- // Then create the ASG that requires the permissions.
263
- const cfnPolicy = policy.node.defaultChild;
264
- cfnAutoScalingGroup.addDependency(cfnPolicy);
265
- /*
266
- `aws` is available via AMIgo baked AMIs.
267
- See https://github.com/guardian/amigo/tree/main/roles/aws-tools.
268
- */
269
- userData.addCommands(`# ${GuEc2AppExperimental.name} UserData Start`, `
270
- TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
271
- INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" "http://169.254.169.254/latest/meta-data/instance-id")
272
-
273
- STATE=$(aws elbv2 describe-target-health \
274
- --target-group-arn ${targetGroup.targetGroupArn} \
275
- --region ${region} \
276
- --targets Id=$INSTANCE_ID,Port=${applicationPort} \
277
- --query "TargetHealthDescriptions[0].TargetHealth.State")
278
-
279
- until [ "$STATE" == "\\"healthy\\"" ]; do
280
- echo "Instance running build ${buildIdentifier} not yet healthy within target group. Current state $STATE. Sleeping..."
281
- sleep ${exports.RollingUpdateDurations.sleep.toSeconds()}
282
- STATE=$(aws elbv2 describe-target-health \
283
- --target-group-arn ${targetGroup.targetGroupArn} \
284
- --region ${region} \
285
- --targets Id=$INSTANCE_ID,Port=${applicationPort} \
286
- --query "TargetHealthDescriptions[0].TargetHealth.State")
287
- done
288
-
289
- echo "Instance running build ${buildIdentifier} is healthy in target group."
290
- `, `# ${GuEc2AppExperimental.name} UserData End`);
291
- userData.addOnExitCommands(`
292
- cfn-signal --stack ${stackId} \
293
- --resource ${cfnAutoScalingGroup.logicalId} \
294
- --region ${region} \
295
- --exit-code $exitCode || echo 'Failed to send Cloudformation Signal'
296
- `);
297
- // If https://github.com/guardian/devx-logs is used, this tag will be added as a marker to logs in Central ELK.
298
- aws_cdk_lib_1.Tags.of(autoScalingGroup.instanceLaunchTemplate).add(constants_1.MetadataKeys.BUILD_IDENTIFIER, buildIdentifier, {
299
- applyToLaunchedInstances: true,
348
+ new GuRoleForRollingUpdateExperimental(scope, { autoScalingGroup });
349
+ new GuUserDataForRollingUpdateExperimental(scope, {
350
+ autoScalingGroup,
351
+ targetGroup,
352
+ applicationPort,
353
+ buildIdentifier,
300
354
  });
301
355
  // TODO Once out of experimental, instantiate these `Aspect`s directly in `GuStack`.
302
- aws_cdk_lib_1.Aspects.of(scope).add(AutoScalingRollingUpdateTimeout.getInstance(scope));
303
- aws_cdk_lib_1.Aspects.of(scope).add(HorizontallyScalingDeploymentProperties.getInstance(scope));
356
+ aws_cdk_lib_1.Aspects.of(scope).add(GuAutoScalingRollingUpdateTimeoutExperimental.getInstance(scope));
357
+ aws_cdk_lib_1.Aspects.of(scope).add(GuHorizontallyScalingDeploymentPropertiesExperimental.getInstance(scope));
304
358
  }
305
359
  }
306
360
  exports.GuEc2AppExperimental = GuEc2AppExperimental;
@@ -1 +1 @@
1
- {"version":3,"file":"ec2-app.js","sourceRoot":"","sources":["../../../src/experimental/patterns/ec2-app.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IAspect } from \"aws-cdk-lib\";\nimport { Aspects, CfnParameter, Duration, Tags } from \"aws-cdk-lib\";\nimport { CfnAutoScalingGroup, CfnScalingPolicy, ScalingProcess, UpdatePolicy } from \"aws-cdk-lib/aws-autoscaling\";\nimport type { CfnPolicy } from \"aws-cdk-lib/aws-iam\";\nimport { Effect, Policy, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport type { IConstruct } from \"constructs\";\nimport { MetadataKeys } from \"../../constants\";\nimport { GuAutoScalingGroup } from \"../../constructs/autoscaling\";\nimport type { GuStack } from \"../../constructs/core\";\nimport type { GuEc2AppProps } from \"../../patterns\";\nimport { GuEc2App } from \"../../patterns\";\nimport { isSingletonPresentInStack } from \"../../utils/singleton\";\n\ninterface AutoScalingRollingUpdateDurations {\n /**\n * The time between each check of an instance's health within the target group.\n */\n sleep: Duration;\n\n /**\n * Additional time to cover the time spent polling an instance's health within the target group before sending the CloudFormation signal.\n */\n buffer: Duration;\n}\n\nexport const RollingUpdateDurations: AutoScalingRollingUpdateDurations = {\n sleep: Duration.seconds(5),\n buffer: Duration.minutes(1),\n};\n\n/**\n * Ensures the `AutoScalingRollingUpdate` of an AutoScaling Group has a `PauseTime` matching the healthcheck grace period.\n * It also ensures the `CreationPolicy` resource signal `Timeout` matches the healthcheck grace period.\n *\n * @internal\n *\n * @privateRemarks\n * The ASG healthcheck grace period is hard-coded by {@link GuEc2App}.\n * Customisation of this value is performed via an escape hatch.\n * An `Aspect` is the only way to observe any customisation.\n *\n * TODO Expose the healthcheck grace period as a property on {@link GuEc2App} and remove this `Aspect`.\n */\nclass AutoScalingRollingUpdateTimeout implements IAspect {\n public readonly stack: GuStack;\n private static instance: AutoScalingRollingUpdateTimeout | undefined;\n\n private constructor(scope: GuStack) {\n this.stack = scope;\n }\n\n public static getInstance(stack: GuStack): AutoScalingRollingUpdateTimeout {\n if (!this.instance || !isSingletonPresentInStack(stack, this.instance)) {\n this.instance = new AutoScalingRollingUpdateTimeout(stack);\n }\n return this.instance;\n }\n\n public visit(construct: IConstruct) {\n if (construct instanceof CfnAutoScalingGroup) {\n const currentRollingUpdate = construct.cfnOptions.updatePolicy?.autoScalingRollingUpdate;\n const currentCreationPolicy = construct.cfnOptions.creationPolicy;\n\n if (!construct.healthCheckGracePeriod) {\n throw new Error(`The healthcheck grace period not set for autoscaling group ${construct.node.id}.`);\n }\n\n const signalTimeoutSeconds = construct.healthCheckGracePeriod + RollingUpdateDurations.buffer.toSeconds();\n\n if (currentRollingUpdate) {\n construct.cfnOptions.updatePolicy = {\n autoScalingRollingUpdate: {\n ...currentRollingUpdate,\n pauseTime: Duration.seconds(signalTimeoutSeconds).toIsoString(),\n },\n };\n }\n\n if (currentCreationPolicy) {\n construct.cfnOptions.creationPolicy = {\n ...currentCreationPolicy,\n resourceSignal: {\n ...currentCreationPolicy.resourceSignal,\n timeout: Duration.seconds(signalTimeoutSeconds).toIsoString(),\n },\n };\n }\n }\n }\n}\n\n/**\n * An `Aspect` that adjusts the properties of an AutoScaling Group using an `AutoScalingRollingUpdate` update policy.\n *\n * It'll unset the `DesiredCapacity` of the ASG as a rolling update sets `Desired` back to the template version,\n * undoing any changes that a scale-out event may have done.\n * Having `DesiredCapacity` unset ensures the service remains at-capacity at all times.\n *\n * It'll also make the `MinInstancesInService` property dynamic via a CFN Parameter that Riff-Raff will set.\n * The value depends on the current capacity of the ASG:\n * - If the service is running normally, it'll be set to the `MinSize` capacity\n * - If the service is partially scaled, it'll be set to the current `DesiredCapacity`\n * - If the service is fully scaled, it'll be set to (at least) `MaxSize` - 1\n *\n * @privateRemarks\n * - Temporarily implemented as a singleton to ensure only one instance is added to a stack,\n * else multiple attempts to add the same CFN Parameter will be made and fail.\n * - Once out of experimental, instantiate this `Aspect` directly in {@link GuStack}.\n *\n * @see https://github.com/guardian/testing-asg-rolling-update\n */\n// eslint-disable-next-line custom-rules/experimental-classes -- this class is not indented for public use\nexport class HorizontallyScalingDeploymentProperties implements IAspect {\n public readonly stack: GuStack;\n public readonly asgToParamMap: Map<string, CfnParameter>;\n private static instance: HorizontallyScalingDeploymentProperties | undefined;\n\n private constructor(scope: GuStack) {\n this.stack = scope;\n this.asgToParamMap = new Map();\n }\n\n public static getInstance(stack: GuStack): HorizontallyScalingDeploymentProperties {\n if (!this.instance || !isSingletonPresentInStack(stack, this.instance)) {\n this.instance = new HorizontallyScalingDeploymentProperties(stack);\n }\n\n return this.instance;\n }\n\n public visit(construct: IConstruct) {\n if (construct instanceof CfnScalingPolicy) {\n const { node } = construct;\n const { scopes, path } = node;\n\n /*\n Requiring a `CfnScalingPolicy` to be created in the scope of a `GuAutoScalingGroup`\n is the most reliable way to associate the two together.\n\n Though the `autoScalingGroupName` property is passed to a `CfnScalingPolicy` when instantiating,\n this does not create a concrete link as AWS CDK sets the value to be the ASG's `Ref`.\n That is, it is a `Token` until it is synthesised.\n This is even if the ASG has an explicit name set.\n\n See also:\n - https://docs.aws.amazon.com/cdk/v2/guide/tokens.html\n - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-autoscalinggroup.html#aws-resource-autoscaling-autoscalinggroup-return-values\n - https://github.com/aws/aws-cdk/blob/f6b649d47f8bc30ca741fbb7a4852d51e8275002/packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts#L1560\n */\n const autoScalingGroup = scopes.find((_): _ is GuAutoScalingGroup => _ instanceof GuAutoScalingGroup);\n\n if (!autoScalingGroup) {\n throw new Error(\n `Failed to detect the autoscaling group relating to the scaling policy on path ${path}. Was it created in the scope of a GuAutoScalingGroup?`,\n );\n }\n\n const cfnAutoScalingGroup = autoScalingGroup.node.defaultChild as CfnAutoScalingGroup;\n const currentRollingUpdate = cfnAutoScalingGroup.cfnOptions.updatePolicy?.autoScalingRollingUpdate;\n\n if (currentRollingUpdate) {\n /*\n An autoscaling group that horizontally scales should not explicitly set `Desired`,\n as a rolling update will set the current `Desired` back to the template version,\n undoing any changes that a scale-out event may have done.\n */\n cfnAutoScalingGroup.desiredCapacity = undefined;\n\n const asgNodeId = autoScalingGroup.node.id;\n\n if (!this.asgToParamMap.has(asgNodeId)) {\n const cfnParameterName = getAsgRollingUpdateCfnParameterName(autoScalingGroup);\n this.asgToParamMap.set(\n asgNodeId,\n new CfnParameter(this.stack, cfnParameterName, {\n type: \"Number\",\n default: parseInt(cfnAutoScalingGroup.minSize),\n maxValue: parseInt(cfnAutoScalingGroup.maxSize) - 1,\n }),\n );\n }\n\n const minInstancesInService = this.asgToParamMap.get(asgNodeId)!;\n\n cfnAutoScalingGroup.cfnOptions.updatePolicy = {\n autoScalingRollingUpdate: {\n ...currentRollingUpdate,\n minInstancesInService: minInstancesInService.valueAsNumber,\n },\n };\n }\n }\n }\n}\n\nexport function getAsgRollingUpdateCfnParameterName(autoScalingGroup: GuAutoScalingGroup) {\n const { app } = autoScalingGroup;\n return `MinInstancesInServiceFor${app.replaceAll(\"-\", \"\")}`;\n}\n\n/**\n * An IAM Policy allowing the sending of a CloudFormation signal.\n *\n * @privateRemarks\n * Implemented as a singleton as the resources can only be tightened at most to the CloudFormation stack.\n */\nclass AsgRollingUpdatePolicy extends Policy {\n private static instance: AsgRollingUpdatePolicy | undefined;\n\n private constructor(scope: GuStack) {\n const { stackId } = scope;\n\n super(scope, \"AsgRollingUpdatePolicy\", {\n statements: [\n // Allow usage of command `cfn-signal`.\n new PolicyStatement({\n actions: [\"cloudformation:SignalResource\"],\n effect: Effect.ALLOW,\n resources: [stackId],\n }),\n\n /*\n Allow usage of command `aws elbv2 describe-target-health`.\n AWS Elastic Load Balancing does not support resource based policies, so the resource has to be `*` (any) here.\n See https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html\n */\n new PolicyStatement({\n actions: [\"elasticloadbalancing:DescribeTargetHealth\"],\n effect: Effect.ALLOW,\n resources: [\"*\"],\n }),\n ],\n });\n }\n\n public static getInstance(stack: GuStack): AsgRollingUpdatePolicy {\n if (!this.instance || !isSingletonPresentInStack(stack, this.instance)) {\n this.instance = new AsgRollingUpdatePolicy(stack);\n }\n\n return this.instance;\n }\n}\n\nexport interface GuEc2AppExperimentalProps extends Omit<GuEc2AppProps, \"updatePolicy\"> {\n /**\n * Which application build to run.\n * This will typically match the build number provided by CI.\n *\n * @example\n * process.env.GITHUB_RUN_NUMBER\n */\n buildIdentifier: string;\n}\n\n/**\n * An experimental pattern to instantiate an EC2 application that is updated entirely via CloudFormation.\n * It sets the update policy of the `AWS::AutoScaling::AutoScalingGroup` to `AutoScalingRollingUpdate`.\n * When a CloudFormation update is applied, the current instances in the ASG will be replaced.\n *\n * This pattern also updates the User Data, running some commands AFTER yours.\n * These changes are wrapped in start and end marking comments.\n *\n * This pattern does not enable for scaling events to trigger _during_ a deployment\n * as during testing it was found that scale-in events during deployment led to unpredictable service availability.\n *\n * If a deployment fails, for example when the healthcheck fails, CloudFormation will automatically rollback the changes.\n * This improves on the behaviour of Riff-Raff's `autoscaling` deployment which requires manual intervention on deployment failure.\n *\n * To update the application's code, a CloudFormation update must be triggered.\n * The best way to do this is to include the build number in the application artifact.\n * TODO test User Data includes a build number.\n *\n * @remarks\n * - The rate at which instances are replaced are conditional based on how scaled up the service is.\n * The more an application is scaled, the longer it'll take to deploy.\n * - It is not necessary to use the `autoscaling` Riff-Raff deployment type in conjunction with this.\n * Doing so won't break anything, however it'll lengthen the deployment time as instances will be rotated twice.\n * Please remove it from any manually created `riff-raff.yaml` file.\n * - Your application should include a build number in its filename.\n * This value will change across builds, and therefore create a CloudFormation template difference to be deployed.\n * - Requires the AWS CLI and `cfn-signal` binaries to be available on the instance, and on the `PATH`.\n * AMIgo adds these via the `aws-tools` role.\n *\n * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html#cfn-attributes-updatepolicy-rollingupdate\n * @see https://github.com/guardian/testing-asg-rolling-update\n *\n * @experimental\n */\nexport class GuEc2AppExperimental extends GuEc2App {\n constructor(scope: GuStack, props: GuEc2AppExperimentalProps) {\n const { minimumInstances, maximumInstances = minimumInstances * 2 } = props.scaling;\n const { applicationPort, buildIdentifier } = props;\n const { region, stackId } = scope;\n\n super(scope, {\n ...props,\n updatePolicy: UpdatePolicy.rollingUpdate({\n maxBatchSize: maximumInstances,\n minInstancesInService: minimumInstances,\n minSuccessPercentage: 100,\n waitOnResourceSignals: true,\n\n /*\n If a scale-in event fires during an `AutoScalingRollingUpdate` operation, the update could fail and rollback.\n For this reason, we suspend the `AlarmNotification` process, else availability of a service cannot be guaranteed.\n Consequently, services cannot scale-out during deployments.\n If AWS ever supports suspending scale-out and scale-in independently, we should allow scale-out.\n */\n suspendProcesses: [ScalingProcess.ALARM_NOTIFICATION],\n }),\n });\n\n const { autoScalingGroup, targetGroup } = this;\n const { userData, role } = autoScalingGroup;\n const cfnAutoScalingGroup = autoScalingGroup.node.defaultChild as CfnAutoScalingGroup;\n\n cfnAutoScalingGroup.desiredCapacity = minimumInstances.toString();\n\n cfnAutoScalingGroup.cfnOptions.creationPolicy = {\n autoScalingCreationPolicy: {\n minSuccessfulInstancesPercent: 100,\n },\n resourceSignal: {\n count: minimumInstances,\n },\n };\n\n const policy = AsgRollingUpdatePolicy.getInstance(scope);\n policy.attachToRole(role);\n\n // Create the Policy with necessary permissions first.\n // Then create the ASG that requires the permissions.\n const cfnPolicy = policy.node.defaultChild as CfnPolicy;\n cfnAutoScalingGroup.addDependency(cfnPolicy);\n\n /*\n `aws` is available via AMIgo baked AMIs.\n See https://github.com/guardian/amigo/tree/main/roles/aws-tools.\n */\n userData.addCommands(\n `# ${GuEc2AppExperimental.name} UserData Start`,\n `\n TOKEN=$(curl -X PUT \"http://169.254.169.254/latest/api/token\" -H \"X-aws-ec2-metadata-token-ttl-seconds: 21600\")\n INSTANCE_ID=$(curl -H \"X-aws-ec2-metadata-token: $TOKEN\" \"http://169.254.169.254/latest/meta-data/instance-id\")\n\n STATE=$(aws elbv2 describe-target-health \\\n --target-group-arn ${targetGroup.targetGroupArn} \\\n --region ${region} \\\n --targets Id=$INSTANCE_ID,Port=${applicationPort} \\\n --query \"TargetHealthDescriptions[0].TargetHealth.State\")\n\n until [ \"$STATE\" == \"\\\\\"healthy\\\\\"\" ]; do\n echo \"Instance running build ${buildIdentifier} not yet healthy within target group. Current state $STATE. Sleeping...\"\n sleep ${RollingUpdateDurations.sleep.toSeconds()}\n STATE=$(aws elbv2 describe-target-health \\\n --target-group-arn ${targetGroup.targetGroupArn} \\\n --region ${region} \\\n --targets Id=$INSTANCE_ID,Port=${applicationPort} \\\n --query \"TargetHealthDescriptions[0].TargetHealth.State\")\n done\n\n echo \"Instance running build ${buildIdentifier} is healthy in target group.\"\n `,\n `# ${GuEc2AppExperimental.name} UserData End`,\n );\n\n userData.addOnExitCommands(\n `\n cfn-signal --stack ${stackId} \\\n --resource ${cfnAutoScalingGroup.logicalId} \\\n --region ${region} \\\n --exit-code $exitCode || echo 'Failed to send Cloudformation Signal'\n `,\n );\n\n // If https://github.com/guardian/devx-logs is used, this tag will be added as a marker to logs in Central ELK.\n Tags.of(autoScalingGroup.instanceLaunchTemplate).add(MetadataKeys.BUILD_IDENTIFIER, buildIdentifier, {\n applyToLaunchedInstances: true,\n });\n\n // TODO Once out of experimental, instantiate these `Aspect`s directly in `GuStack`.\n Aspects.of(scope).add(AutoScalingRollingUpdateTimeout.getInstance(scope));\n Aspects.of(scope).add(HorizontallyScalingDeploymentProperties.getInstance(scope));\n }\n}\n"]}
1
+ {"version":3,"file":"ec2-app.js","sourceRoot":"","sources":["../../../src/experimental/patterns/ec2-app.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IAspect } from \"aws-cdk-lib\";\nimport { Aspects, CfnParameter, Duration, Tags } from \"aws-cdk-lib\";\nimport { CfnAutoScalingGroup, CfnScalingPolicy, ScalingProcess, UpdatePolicy } from \"aws-cdk-lib/aws-autoscaling\";\nimport type { CfnPolicy } from \"aws-cdk-lib/aws-iam\";\nimport { Effect, Policy, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport type { IConstruct } from \"constructs\";\nimport { MetadataKeys } from \"../../constants\";\nimport { GuAutoScalingGroup } from \"../../constructs/autoscaling\";\nimport type { GuStack } from \"../../constructs/core\";\nimport type { GuApplicationTargetGroup } from \"../../constructs/loadbalancing\";\nimport type { GuEc2AppProps } from \"../../patterns\";\nimport { GuEc2App } from \"../../patterns\";\nimport { isSingletonPresentInStack } from \"../../utils/singleton\";\n\ninterface AutoScalingRollingUpdateDurations {\n /**\n * The time between each check of an instance's health within the target group.\n */\n sleep: Duration;\n\n /**\n * Additional time to cover the time spent polling an instance's health within the target group before sending the CloudFormation signal.\n */\n buffer: Duration;\n}\n\nexport const RollingUpdateDurations: AutoScalingRollingUpdateDurations = {\n sleep: Duration.seconds(5),\n buffer: Duration.minutes(1),\n};\n\n/**\n * Ensures the `AutoScalingRollingUpdate` of an AutoScaling Group has a `PauseTime` matching the healthcheck grace period.\n * It also ensures the `CreationPolicy` resource signal `Timeout` matches the healthcheck grace period.\n *\n * @internal\n *\n * @privateRemarks\n * The ASG healthcheck grace period is hard-coded by {@link GuEc2App}.\n * Customisation of this value is performed via an escape hatch.\n * An `Aspect` is the only way to observe any customisation.\n *\n * TODO Expose the healthcheck grace period as a property on {@link GuEc2App} and remove this `Aspect`.\n */\nexport class GuAutoScalingRollingUpdateTimeoutExperimental implements IAspect {\n public readonly stack: GuStack;\n private static instance: GuAutoScalingRollingUpdateTimeoutExperimental | undefined;\n\n private constructor(scope: GuStack) {\n this.stack = scope;\n }\n\n public static getInstance(stack: GuStack): GuAutoScalingRollingUpdateTimeoutExperimental {\n if (!this.instance || !isSingletonPresentInStack(stack, this.instance)) {\n this.instance = new GuAutoScalingRollingUpdateTimeoutExperimental(stack);\n }\n return this.instance;\n }\n\n public visit(construct: IConstruct) {\n if (construct instanceof CfnAutoScalingGroup) {\n const currentRollingUpdate = construct.cfnOptions.updatePolicy?.autoScalingRollingUpdate;\n const currentCreationPolicy = construct.cfnOptions.creationPolicy;\n\n if (!construct.healthCheckGracePeriod) {\n throw new Error(`The healthcheck grace period not set for autoscaling group ${construct.node.id}.`);\n }\n\n const signalTimeoutSeconds = construct.healthCheckGracePeriod + RollingUpdateDurations.buffer.toSeconds();\n\n if (currentRollingUpdate) {\n construct.cfnOptions.updatePolicy = {\n autoScalingRollingUpdate: {\n ...currentRollingUpdate,\n pauseTime: Duration.seconds(signalTimeoutSeconds).toIsoString(),\n },\n };\n }\n\n if (currentCreationPolicy) {\n construct.cfnOptions.creationPolicy = {\n ...currentCreationPolicy,\n resourceSignal: {\n ...currentCreationPolicy.resourceSignal,\n timeout: Duration.seconds(signalTimeoutSeconds).toIsoString(),\n },\n };\n }\n }\n }\n}\n\n/**\n * An `Aspect` that adjusts the properties of an AutoScaling Group using an `AutoScalingRollingUpdate` update policy.\n *\n * It'll unset the `DesiredCapacity` of the ASG as a rolling update sets `Desired` back to the template version,\n * undoing any changes that a scale-out event may have done.\n * Having `DesiredCapacity` unset ensures the service remains at-capacity at all times.\n *\n * It'll also make the `MinInstancesInService` property dynamic via a CFN Parameter that Riff-Raff will set.\n * The value depends on the current capacity of the ASG:\n * - If the service is running normally, it'll be set to the `MinSize` capacity\n * - If the service is partially scaled, it'll be set to the current `DesiredCapacity`\n * - If the service is fully scaled, it'll be set to (at least) `MaxSize` - 1\n *\n * @privateRemarks\n * - Temporarily implemented as a singleton to ensure only one instance is added to a stack,\n * else multiple attempts to add the same CFN Parameter will be made and fail.\n * - Once out of experimental, instantiate this `Aspect` directly in {@link GuStack}.\n *\n * @see https://github.com/guardian/testing-asg-rolling-update\n */\nexport class GuHorizontallyScalingDeploymentPropertiesExperimental implements IAspect {\n public readonly stack: GuStack;\n public readonly asgToParamMap: Map<string, CfnParameter>;\n private static instance: GuHorizontallyScalingDeploymentPropertiesExperimental | undefined;\n\n private constructor(scope: GuStack) {\n this.stack = scope;\n this.asgToParamMap = new Map();\n }\n\n public static getInstance(stack: GuStack): GuHorizontallyScalingDeploymentPropertiesExperimental {\n if (!this.instance || !isSingletonPresentInStack(stack, this.instance)) {\n this.instance = new GuHorizontallyScalingDeploymentPropertiesExperimental(stack);\n }\n\n return this.instance;\n }\n\n public visit(construct: IConstruct) {\n if (construct instanceof CfnScalingPolicy) {\n const { node } = construct;\n const { scopes, path } = node;\n\n /*\n Requiring a `CfnScalingPolicy` to be created in the scope of a `GuAutoScalingGroup`\n is the most reliable way to associate the two together.\n\n Though the `autoScalingGroupName` property is passed to a `CfnScalingPolicy` when instantiating,\n this does not create a concrete link as AWS CDK sets the value to be the ASG's `Ref`.\n That is, it is a `Token` until it is synthesised.\n This is even if the ASG has an explicit name set.\n\n See also:\n - https://docs.aws.amazon.com/cdk/v2/guide/tokens.html\n - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-autoscalinggroup.html#aws-resource-autoscaling-autoscalinggroup-return-values\n - https://github.com/aws/aws-cdk/blob/f6b649d47f8bc30ca741fbb7a4852d51e8275002/packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts#L1560\n */\n const autoScalingGroup = scopes.find((_): _ is GuAutoScalingGroup => _ instanceof GuAutoScalingGroup);\n\n if (!autoScalingGroup) {\n throw new Error(\n `Failed to detect the autoscaling group relating to the scaling policy on path ${path}. Was it created in the scope of a GuAutoScalingGroup?`,\n );\n }\n\n const cfnAutoScalingGroup = autoScalingGroup.node.defaultChild as CfnAutoScalingGroup;\n const currentRollingUpdate = cfnAutoScalingGroup.cfnOptions.updatePolicy?.autoScalingRollingUpdate;\n\n if (currentRollingUpdate) {\n /*\n An autoscaling group that horizontally scales should not explicitly set `Desired`,\n as a rolling update will set the current `Desired` back to the template version,\n undoing any changes that a scale-out event may have done.\n */\n cfnAutoScalingGroup.desiredCapacity = undefined;\n\n const asgNodeId = autoScalingGroup.node.id;\n\n if (!this.asgToParamMap.has(asgNodeId)) {\n const cfnParameterName = getAsgRollingUpdateCfnParameterName(autoScalingGroup);\n this.asgToParamMap.set(\n asgNodeId,\n new CfnParameter(this.stack, cfnParameterName, {\n type: \"Number\",\n default: parseInt(cfnAutoScalingGroup.minSize),\n maxValue: parseInt(cfnAutoScalingGroup.maxSize) - 1,\n }),\n );\n }\n\n const minInstancesInService = this.asgToParamMap.get(asgNodeId)!;\n\n cfnAutoScalingGroup.cfnOptions.updatePolicy = {\n autoScalingRollingUpdate: {\n ...currentRollingUpdate,\n minInstancesInService: minInstancesInService.valueAsNumber,\n },\n };\n }\n }\n }\n}\n\nexport function getAsgRollingUpdateCfnParameterName(autoScalingGroup: GuAutoScalingGroup) {\n const { app } = autoScalingGroup;\n return `MinInstancesInServiceFor${app.replaceAll(\"-\", \"\")}`;\n}\n\n/**\n * An IAM Policy allowing the sending of a CloudFormation signal.\n *\n * @privateRemarks\n * Implemented as a singleton as the resources can only be tightened at most to the CloudFormation stack.\n */\nclass AsgRollingUpdatePolicy extends Policy {\n private static instance: AsgRollingUpdatePolicy | undefined;\n\n private constructor(scope: GuStack) {\n const { stackId } = scope;\n\n super(scope, \"AsgRollingUpdatePolicy\", {\n statements: [\n // Allow usage of command `cfn-signal`.\n new PolicyStatement({\n actions: [\"cloudformation:SignalResource\"],\n effect: Effect.ALLOW,\n resources: [stackId],\n }),\n\n /*\n Allow usage of command `aws elbv2 describe-target-health`.\n AWS Elastic Load Balancing does not support resource based policies, so the resource has to be `*` (any) here.\n See https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html\n */\n new PolicyStatement({\n actions: [\"elasticloadbalancing:DescribeTargetHealth\"],\n effect: Effect.ALLOW,\n resources: [\"*\"],\n }),\n ],\n });\n }\n\n public static getInstance(stack: GuStack): AsgRollingUpdatePolicy {\n if (!this.instance || !isSingletonPresentInStack(stack, this.instance)) {\n this.instance = new AsgRollingUpdatePolicy(stack);\n }\n\n return this.instance;\n }\n}\n\nexport interface GuEc2AppExperimentalProps extends Omit<GuEc2AppProps, \"updatePolicy\"> {\n /**\n * Which application build to run.\n * This will typically match the build number provided by CI.\n *\n * @example\n * process.env.GITHUB_RUN_NUMBER\n */\n buildIdentifier: string;\n}\n\n/**\n * Configures the rolling update policy for the autoscaling group.\n *\n * Most applications should not need to instantiate this class directly as this policy is set by GuEc2AppExperimental.\n *\n * If you need to use this class for a service other than MAPI, please speak to DevX about your use-case first.\n */\nexport class GuRollingUpdatePolicyExperimental {\n static getPolicy(maximumInstances: number, minimumInstances?: number) {\n return UpdatePolicy.rollingUpdate({\n maxBatchSize: maximumInstances,\n minInstancesInService: minimumInstances,\n minSuccessPercentage: 100,\n waitOnResourceSignals: true,\n /*\n If a scale-in event fires during an `AutoScalingRollingUpdate` operation, the update could fail and rollback.\n For this reason, we suspend the `AlarmNotification` process, else availability of a service cannot be guaranteed.\n Consequently, services cannot scale-out during deployments.\n If AWS ever supports suspending scale-out and scale-in independently, we should allow scale-out.\n */\n suspendProcesses: [ScalingProcess.ALARM_NOTIFICATION],\n // Note: there is also an important property called pauseTime, but this is set via an Aspect.\n });\n }\n}\n\nexport interface GuRoleForRollingUpdateExperimentalProps {\n autoScalingGroup: GuAutoScalingGroup;\n}\n\n/**\n * Grants the role associated with your autoscaling group the necessary permissions for the rolling update deployment\n * mechanism.\n *\n * Most applications should not need to instantiate this class directly as these permission changes are granted\n * by GuEc2AppExperimental.\n *\n * If you need to use this class for a service other than MAPI, please speak to DevX about your use-case first.\n */\nexport class GuRoleForRollingUpdateExperimental {\n constructor(scope: GuStack, props: GuRoleForRollingUpdateExperimentalProps) {\n const policy = AsgRollingUpdatePolicy.getInstance(scope);\n policy.attachToRole(props.autoScalingGroup.role);\n\n // Create the Policy with necessary permissions first.\n // Then create the ASG that requires the permissions.\n const cfnPolicy = policy.node.defaultChild as CfnPolicy;\n const cfnAutoScalingGroup = props.autoScalingGroup.node.defaultChild as CfnAutoScalingGroup;\n cfnAutoScalingGroup.addDependency(cfnPolicy);\n }\n}\n\nexport interface GuUserDataForRollingUpdateExperimentalProps {\n autoScalingGroup: GuAutoScalingGroup;\n targetGroup: GuApplicationTargetGroup;\n applicationPort: number;\n buildIdentifier: string;\n}\n\n/**\n * Modifies the user-data script (which runs whenever an instance is launched) to make it compatible with the new\n * rolling update deployment mechanism.\n *\n * Most applications should not need to instantiate this class directly as these user-data changes are already made\n * by GuEc2AppExperimental.\n *\n * If you need to use this class for a service other than MAPI, please speak to DevX about your use-case first.\n */\nexport class GuUserDataForRollingUpdateExperimental {\n constructor(scope: GuStack, props: GuUserDataForRollingUpdateExperimentalProps) {\n const { region, stackId } = scope;\n const { autoScalingGroup, targetGroup, applicationPort, buildIdentifier } = props;\n const cfnAutoScalingGroup = autoScalingGroup.node.defaultChild as CfnAutoScalingGroup;\n\n /*\n `aws` is available via AMIgo baked AMIs.\n See https://github.com/guardian/amigo/tree/main/roles/aws-tools.\n */\n autoScalingGroup.userData.addCommands(\n `# ${GuEc2AppExperimental.name} UserData Start`,\n `\n TOKEN=$(curl -X PUT \"http://169.254.169.254/latest/api/token\" -H \"X-aws-ec2-metadata-token-ttl-seconds: 21600\")\n INSTANCE_ID=$(curl -H \"X-aws-ec2-metadata-token: $TOKEN\" \"http://169.254.169.254/latest/meta-data/instance-id\")\n\n STATE=$(aws elbv2 describe-target-health \\\n --target-group-arn ${targetGroup.targetGroupArn} \\\n --region ${region} \\\n --targets Id=$INSTANCE_ID,Port=${applicationPort} \\\n --query \"TargetHealthDescriptions[0].TargetHealth.State\")\n\n until [ \"$STATE\" == \"\\\\\"healthy\\\\\"\" ]; do\n echo \"Instance running build ${buildIdentifier} not yet healthy within target group. Current state $STATE. Sleeping...\"\n sleep ${RollingUpdateDurations.sleep.toSeconds()}\n STATE=$(aws elbv2 describe-target-health \\\n --target-group-arn ${targetGroup.targetGroupArn} \\\n --region ${region} \\\n --targets Id=$INSTANCE_ID,Port=${applicationPort} \\\n --query \"TargetHealthDescriptions[0].TargetHealth.State\")\n done\n\n echo \"Instance running build ${buildIdentifier} is healthy in target group.\"\n `,\n `# ${GuEc2AppExperimental.name} UserData End`,\n );\n\n autoScalingGroup.userData.addOnExitCommands(\n `\n cfn-signal --stack ${stackId} \\\n --resource ${cfnAutoScalingGroup.logicalId} \\\n --region ${region} \\\n --exit-code $exitCode || echo 'Failed to send Cloudformation Signal'\n `,\n );\n\n // If https://github.com/guardian/devx-logs is used, this tag will be added as a marker to logs in Central ELK.\n Tags.of(autoScalingGroup.instanceLaunchTemplate).add(MetadataKeys.BUILD_IDENTIFIER, buildIdentifier, {\n applyToLaunchedInstances: true,\n });\n }\n}\n\n/**\n * An experimental pattern to instantiate an EC2 application that is updated entirely via CloudFormation.\n * It sets the update policy of the `AWS::AutoScaling::AutoScalingGroup` to `AutoScalingRollingUpdate`.\n * When a CloudFormation update is applied, the current instances in the ASG will be replaced.\n *\n * This pattern also updates the User Data, running some commands AFTER yours.\n * These changes are wrapped in start and end marking comments.\n *\n * This pattern does not enable for scaling events to trigger _during_ a deployment\n * as during testing it was found that scale-in events during deployment led to unpredictable service availability.\n *\n * If a deployment fails, for example when the healthcheck fails, CloudFormation will automatically rollback the changes.\n * This improves on the behaviour of Riff-Raff's `autoscaling` deployment which requires manual intervention on deployment failure.\n *\n * To update the application's code, a CloudFormation update must be triggered.\n * The best way to do this is to include the build number in the application artifact.\n * TODO test User Data includes a build number.\n *\n * @remarks\n * - The rate at which instances are replaced are conditional based on how scaled up the service is.\n * The more an application is scaled, the longer it'll take to deploy.\n * - It is not necessary to use the `autoscaling` Riff-Raff deployment type in conjunction with this.\n * Doing so won't break anything, however it'll lengthen the deployment time as instances will be rotated twice.\n * Please remove it from any manually created `riff-raff.yaml` file.\n * - Your application should include a build number in its filename.\n * This value will change across builds, and therefore create a CloudFormation template difference to be deployed.\n * - Requires the AWS CLI and `cfn-signal` binaries to be available on the instance, and on the `PATH`.\n * AMIgo adds these via the `aws-tools` role.\n *\n * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html#cfn-attributes-updatepolicy-rollingupdate\n * @see https://github.com/guardian/testing-asg-rolling-update\n *\n * @experimental\n */\nexport class GuEc2AppExperimental extends GuEc2App {\n constructor(scope: GuStack, props: GuEc2AppExperimentalProps) {\n const { minimumInstances, maximumInstances = minimumInstances * 2 } = props.scaling;\n const { applicationPort, buildIdentifier } = props;\n\n super(scope, {\n ...props,\n // Note: if the service uses horizontal scaling, minimumInstances is overridden by an Aspect (to prevent accidental scale downs!)\n updatePolicy: GuRollingUpdatePolicyExperimental.getPolicy(maximumInstances, minimumInstances),\n });\n\n const { autoScalingGroup, targetGroup } = this;\n const cfnAutoScalingGroup = autoScalingGroup.node.defaultChild as CfnAutoScalingGroup;\n\n // Note: if the service uses horizontal scaling, this property is unset by an Aspect (to prevent accidental scale downs!)\n cfnAutoScalingGroup.desiredCapacity = minimumInstances.toString();\n\n // This is used when an ASG is first created; it is not needed to support the new deployment mechanism\n cfnAutoScalingGroup.cfnOptions.creationPolicy = {\n autoScalingCreationPolicy: {\n minSuccessfulInstancesPercent: 100,\n },\n resourceSignal: {\n count: minimumInstances,\n },\n };\n\n new GuRoleForRollingUpdateExperimental(scope, { autoScalingGroup });\n new GuUserDataForRollingUpdateExperimental(scope, {\n autoScalingGroup,\n targetGroup,\n applicationPort,\n buildIdentifier,\n });\n\n // TODO Once out of experimental, instantiate these `Aspect`s directly in `GuStack`.\n Aspects.of(scope).add(GuAutoScalingRollingUpdateTimeoutExperimental.getInstance(scope));\n Aspects.of(scope).add(GuHorizontallyScalingDeploymentPropertiesExperimental.getInstance(scope));\n }\n}\n"]}
@@ -264,12 +264,6 @@ export interface GuEc2AppProps extends AppIdentity {
264
264
  * and must rely on riffraff to do so.
265
265
  */
266
266
  updatePolicy?: UpdatePolicy;
267
- /**
268
- * This setting configures the launch template to enable or disable detailed monitoring on instances.
269
- *
270
- * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-monitoring.html
271
- */
272
- enabledDetailedInstanceMonitoring?: boolean;
273
267
  /**
274
268
  * You can specify how long after an instance reaches the InService state it waits before contributing
275
269
  * usage data to the aggregated metrics. This specified time is called the default instance warmup.
@@ -279,6 +273,16 @@ export interface GuEc2AppProps extends AppIdentity {
279
273
  * @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-default-instance-warmup.html
280
274
  */
281
275
  defaultInstanceWarmup?: Duration;
276
+ /**
277
+ * How often to send EC2 metrics, such as CPU usage.
278
+ * By default, AWS will produce `5Minute` granular metrics.
279
+ *
280
+ * It is recommended to produce `1Minute` granular metrics in production,
281
+ * especially when using ASG metrics to trigger horizontal scaling as it allows for earlier scaling.
282
+ *
283
+ * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html
284
+ */
285
+ instanceMetricGranularity: "1Minute" | "5Minute";
282
286
  }
283
287
  /**
284
288
  * Pattern which creates the resources needed to run an application on EC2
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/patterns/ec2-app/base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/patterns/ec2-app/base.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAQ7E,OAAO,KAAK,EAAE,YAAY,EAAS,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAQ,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,IAAI,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAO5F,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAc,MAAM,8BAA8B,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAMtG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAgE,MAAM,uBAAuB,CAAC;AAElH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhE,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,0BAA0B,EAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,YAAY,EAAE,KAAK,GAAG,iBAAiB,CAAC;IACxC;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,GAAG,iBAAiB,CAAC;IACzC;;OAEG;IACH,uBAAuB,EAAE,OAAO,CAAC;IACjC;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD;;OAEG;IACH,QAAQ,EAAE,eAAe,GAAG,QAAQ,CAAC;IACrC;;;;;;OAMG;IACH,MAAM,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC;;OAEG;IACH,uBAAuB,EAAE,MAAM,GAAG,YAAY,CAAC;IAC/C;;;OAGG;IACH,YAAY,EAAE,YAAY,CAAC;IAC3B;;OAEG;IACH,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C;;OAEG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC;;OAEG;IACH,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;IAC7B;;OAEG;IACH,OAAO,EAAE,aAAa,CAAC;IACvB;;OAEG;IACH,gBAAgB,CAAC,EAAE,YAAY,CAAC;IAEhC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAClC;;;;OAIG;IACH,GAAG,CAAC,EAAE,IAAI,CAAC;IACX;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;IAE3B;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC;IAE1B;;OAEG;IACH,UAAU,CAAC,EAAE;QACX;;;;;;;;;;;;;;;;WAgBG;QACH,OAAO,EAAE,IAAI,CAAC;QACd;;;;WAIG;QACH,MAAM,EAAE,MAAM,CAAC;QACf;;;;;;;;;;WAUG;QACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;QACzB;;;;;;;;;WASG;QACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;QACjC;;;;;;;;;;;;;;;;;;WAkBG;QACH,6BAA6B,CAAC,EAAE,MAAM,CAAC;QAEvC;;;;;;WAMG;QACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IAEF;;OAEG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;IAE7B;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAElC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAE5B;;;;;;;OAOG;IACH,qBAAqB,CAAC,EAAE,QAAQ,CAAC;IAEjC;;;;;;;;OAQG;IACH,yBAAyB,EAAE,SAAS,GAAG,SAAS,CAAC;CAClD;AAUD;;;;;;GAMG;AACH,qBAAa,QAAS,SAAQ,SAAS;IASrC,SAAgB,GAAG,EAAE,IAAI,CAAC;IAC1B,SAAgB,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5C,SAAgB,YAAY,EAAE,yBAAyB,CAAC;IACxD,SAAgB,gBAAgB,EAAE,kBAAkB,CAAC;IACrD,SAAgB,QAAQ,EAAE,0BAA0B,CAAC;IACrD,SAAgB,WAAW,EAAE,wBAAwB,CAAC;gBAE1C,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa;CAqUjD"}
@@ -42,7 +42,7 @@ class GuEc2App extends constructs_1.Construct {
42
42
  constructor(scope, props) {
43
43
  const { access, accessLogging = { enabled: false }, app,
44
44
  // We should update this default once a significant number of apps have migrated to devx-logs
45
- applicationLogging = { enabled: false }, applicationPort, blockDevices, certificateProps, instanceType, monitoringConfiguration, roleConfiguration = { withoutLogShipping: false, additionalPolicies: [] }, scaling: { minimumInstances, maximumInstances = minimumInstances * 2 }, userData: userDataLike, imageRecipe, vpc = ec2_1.GuVpc.fromIdParameter(scope, core_1.AppIdentity.suffixText({ app }, "VPC")), privateSubnets = ec2_1.GuVpc.subnetsFromParameter(scope, { type: ec2_1.SubnetType.PRIVATE, app }), publicSubnets = ec2_1.GuVpc.subnetsFromParameter(scope, { type: ec2_1.SubnetType.PUBLIC, app }), instanceMetadataHopLimit, updatePolicy, enabledDetailedInstanceMonitoring, defaultInstanceWarmup, } = props;
45
+ applicationLogging = { enabled: false }, applicationPort, blockDevices, certificateProps, instanceType, monitoringConfiguration, roleConfiguration = { withoutLogShipping: false, additionalPolicies: [] }, scaling: { minimumInstances, maximumInstances = minimumInstances * 2 }, userData: userDataLike, imageRecipe, vpc = ec2_1.GuVpc.fromIdParameter(scope, core_1.AppIdentity.suffixText({ app }, "VPC")), privateSubnets = ec2_1.GuVpc.subnetsFromParameter(scope, { type: ec2_1.SubnetType.PRIVATE, app }), publicSubnets = ec2_1.GuVpc.subnetsFromParameter(scope, { type: ec2_1.SubnetType.PUBLIC, app }), instanceMetadataHopLimit, updatePolicy, defaultInstanceWarmup, instanceMetricGranularity, } = props;
46
46
  super(scope, app); // The assumption is `app` is unique
47
47
  // We should really prevent users from doing this via the type system,
48
48
  // but that requires a breaking change to the API
@@ -84,8 +84,8 @@ class GuEc2App extends constructs_1.Construct {
84
84
  imageRecipe,
85
85
  httpPutResponseHopLimit: instanceMetadataHopLimit,
86
86
  updatePolicy,
87
- enabledDetailedInstanceMonitoring,
88
87
  defaultInstanceWarmup,
88
+ instanceMetricGranularity,
89
89
  });
90
90
  // This allows automatic shipping of instance Cloud Init logs when using the
91
91
  // `cdk-base` Amigo role on your AMI.
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/patterns/ec2-app/base.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint \"@guardian/tsdoc-required/tsdoc-required\": 2 -- to begin rolling this out for public APIs. */\nimport { Duration, SecretValue, Tags } from \"aws-cdk-lib\";\nimport type { BlockDevice, UpdatePolicy } from \"aws-cdk-lib/aws-autoscaling\";\nimport { AdditionalHealthCheckType, HealthChecks } from \"aws-cdk-lib/aws-autoscaling\";\nimport {\n ProviderAttribute,\n UserPool,\n UserPoolClientIdentityProvider,\n UserPoolIdentityProviderGoogle,\n} from \"aws-cdk-lib/aws-cognito\";\nimport type { InstanceType, IPeer, ISubnet, IVpc } from \"aws-cdk-lib/aws-ec2\";\nimport { Port, UserData } from \"aws-cdk-lib/aws-ec2\";\nimport type { HealthCheck as ALBHealthCheck } from \"aws-cdk-lib/aws-elasticloadbalancingv2\";\nimport { ApplicationProtocol, ListenerAction } from \"aws-cdk-lib/aws-elasticloadbalancingv2\";\nimport { AuthenticateCognitoAction } from \"aws-cdk-lib/aws-elasticloadbalancingv2-actions\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Architecture, Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { Bucket } from \"aws-cdk-lib/aws-s3\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport { AccessScope, MetadataKeys, NAMED_SSM_PARAMETER_PATHS } from \"../../constants\";\nimport { GuCertificate } from \"../../constructs/acm\";\nimport type { GuUserDataProps } from \"../../constructs/autoscaling\";\nimport { GuAutoScalingGroup, GuUserData } from \"../../constructs/autoscaling\";\nimport type { Http4xxAlarmProps, Http5xxAlarmProps, NoMonitoring } from \"../../constructs/cloudwatch\";\nimport {\n GuAlb4xxPercentageAlarm,\n GuAlb5xxPercentageAlarm,\n GuUnhealthyInstancesAlarm,\n} from \"../../constructs/cloudwatch\";\nimport type { GuStack } from \"../../constructs/core\";\nimport { AppIdentity, GuAccessLoggingBucketParameter, GuLoggingStreamNameParameter } from \"../../constructs/core\";\nimport { GuHttpsEgressSecurityGroup, GuSecurityGroup, GuVpc, SubnetType } from \"../../constructs/ec2\";\nimport type { GuInstanceRoleProps } from \"../../constructs/iam\";\nimport { GuGetPrivateConfigPolicy, GuInstanceRole } from \"../../constructs/iam\";\nimport { GuLambdaFunction } from \"../../constructs/lambda\";\nimport {\n GuApplicationLoadBalancer,\n GuApplicationTargetGroup,\n GuHttpsApplicationListener,\n} from \"../../constructs/loadbalancing\";\nimport { AppAccess } from \"../../types\";\nimport type { GuAsgCapacity, GuDomainName } from \"../../types\";\nimport type { AmigoProps } from \"../../types/amigo\";\nimport { getUserPoolDomainPrefix } from \"../../utils/cognito/cognito\";\n\nexport interface AccessLoggingProps {\n /**\n * Enable (load balancer) access logs.\n *\n * Note, you will need to specify a region in your stack declaration to use\n * this.\n * See`https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticloadbalancingv2.ApplicationLoadBalancer.html#logwbraccesswbrlogsbucket-prefix`\n */\n enabled: boolean;\n /**\n * S3 prefix for the logs.\n *\n * @defaultValue no prefix\n */\n prefix?: string;\n}\n\n/**\n * To ship your application logs to ELK automatically, you must:\n *\n * 1. Set the `enabled` flag to true\n * 2. Include the `cdk-base` Amigo role in your AMI\n * 3. Log to `journald`. We recommend doing this by logging to `stdout` and\n * using `systemd` to start your app\n * 4. Confirm that your [[`systemdUnitName`]] is configured properly.\n *\n * Unless you have explicitly opted-out, appropriate IAM permissions for logging\n * to Kinesis will be configured automatically via the [[`GuEc2App`]] pattern.\n */\nexport interface ApplicationLoggingProps {\n /**\n * Enables forwarding of application logs to the Guardian ELK stack.\n *\n * Note, to work, you will need to also do the following non-CDK things:\n *\n * 1. Include the `cdk-base` Amigo role in your AMI.\n * 2. Log to `journald`. We recommend doing this by logging to `stdout` and\n * using `systemd` to start your app\n * 3. Confirm that your [[`systemdUnitName`]] is configured properly.\n *\n * Unless you have explicitly opted-out, appropriate IAM permissions for\n * logging to Kinesis will be configured automatically via the [[`GuEc2App`]]\n * pattern.\n *\n * @see https://github.com/guardian/amigo/tree/main/roles/cdk-base\n */\n enabled: boolean;\n /**\n * This needs to match the name of your SystemD unit.\n *\n * If your systemd unit is not `<app>.service` set this value.\n *\n * @defaultValue `<app>.service`\n */\n systemdUnitName?: string;\n}\n\n/**\n * @privateRemarks we should rename this to AlarmsProps.\n */\nexport interface Alarms {\n /**\n * Name of the target (SNS Topic) for alarm notifications.\n */\n snsTopicName: string;\n /**\n * Enable the 5xx alarm with settings.\n */\n http5xxAlarm: false | Http5xxAlarmProps;\n /**\n * Enable the 4xx alarm with settings.\n */\n http4xxAlarm?: false | Http4xxAlarmProps;\n /**\n * Enable the unhealthy instances alarm.\n */\n unhealthyInstancesAlarm: boolean;\n /**\n * Internal flag - users of this library should ignore this setting.\n */\n noMonitoring?: false;\n}\n\nexport interface GuEc2AppProps extends AppIdentity {\n /**\n * User data for the autoscaling group.\n */\n userData: GuUserDataProps | UserData;\n /**\n * Network access restrictions for your load balancer.\n *\n * Note, this merely provides defence in depth; you should, for example, limit\n * access to the VPN and then treat that as sufficient. Instead, use Google\n * Auth for human access, or a suitable machine auth mechanism.\n */\n access: AppAccess;\n /**\n * The port your application runs on.\n */\n applicationPort: number;\n /**\n * Configure IAM roles for autoscaling group EC2 instances.\n */\n roleConfiguration?: GuInstanceRoleProps;\n /**\n * Enable and configure alarms.\n */\n monitoringConfiguration: Alarms | NoMonitoring;\n /**\n * EC2 instance type. Note, ensure your code is built for the same\n * architecture family (arm64 - 'Graviton' instances - or x64).\n */\n instanceType: InstanceType;\n /**\n * Enable and configures application logs.\n */\n applicationLogging?: ApplicationLoggingProps;\n /**\n * Enable and configures access logs.\n */\n accessLogging?: AccessLoggingProps;\n /**\n * Add block devices (additional storage).\n */\n blockDevices?: BlockDevice[];\n /**\n * Autoscaling group min and max sizes.\n */\n scaling: GuAsgCapacity;\n /**\n * Specify certificate for the load balancer.\n */\n certificateProps?: GuDomainName;\n\n /**\n * Configure AMIgo image recipe. This is only necessary if you are using GuCDK to generate your riff-raff.yaml file.\n */\n imageRecipe?: string | AmigoProps;\n /**\n * Specify the VPC to use.\n *\n * @see https://github.com/guardian/aws-account-setup\n */\n vpc?: IVpc;\n /**\n * Specify private subnets if using a non-default VPC or (generally\n * discouraged) to limit to a subset of the available subnets.\n */\n privateSubnets?: ISubnet[];\n\n /**\n * Specify private subnets if using a non-default VPC or (generally\n * discouraged) to limit to a subset of the available subnets.\n */\n publicSubnets?: ISubnet[];\n\n /**\n * Configure Google Auth.\n */\n googleAuth?: {\n /**\n * Enables Google Auth (via Cognito). **Additional MANUAL steps required -\n * see below.**\n *\n * Limits access to members of the allowed Google groups.\n *\n * Note, this does not currently support simultaneous machine access, so\n * only set to true if you only require staff access to your service, or are\n * supporting machine access in some other way.\n *\n * MANUAL STEPS: to get this to work, we need a Google Project and\n * associated credentials. Full instructions can be found here:\n *\n * https://docs.google.com/document/d/1_k1FSE52AZHXufWLTiKTI3xy5cGpziyHazSHTKrYfco/edit?usp=sharing\n *\n * DevX hope to automate this process in the near future.\n */\n enabled: true;\n /**\n * The domain users will access your service.\n *\n * Set this to the same as for certificateProps.\n */\n domain: string;\n /**\n * Groups used for membership checks.\n *\n * If specified, cannot be empty. Users must be a member of at least one\n * group to gain access.\n *\n * WARNING: groups must be specified with the `guardian.co.uk` domain, even\n * if that is the non-idiomatic choice for daily use.\n *\n * @defaultValue [`engineering@guardian.co.uk`]\n */\n allowedGroups?: string[];\n /**\n * The number of minutes before the session expires.\n *\n * Set this value to a safe period of time that revoked users\n * sessions will continue to function.\n *\n * NOTE: This value cannot be larger than 60 minutes.\n *\n * @defaultValue 15\n */\n sessionTimeoutInMinutes?: number;\n /**\n * Secrets Manager path containing Google OAuth2 Client credentials.\n *\n * NOTE: you do not need to set this value, but you DO need to generate and\n * store the associated credentials in Secrets Manager.\n *\n * Credentials should be stored in Secrets Manager as JSON:\n *\n * ```json\n * {\n * \"clientId\": \"my-client-id\",\n * \"clientSecret\": \"my-client-secret\"\n * }\n * ```\n *\n * @see `googleAuth.enabled` for how to generate.\n *\n * @defaultValue /:STAGE/:stack/:app/google-auth-credentials\n */\n credentialsSecretsManagerPath?: string;\n\n /**\n * When using Auth in the ALB, which stage of cognito-lambda to use.\n *\n * For most applications this should always be PROD, even in the CODE environments.\n *\n * @defaultValue PROD\n */\n cognitoAuthStage?: string;\n };\n\n /**\n * Specify custom healthcheck\n */\n healthcheck?: ALBHealthCheck;\n\n /**\n * Set http put response hop limit for the launch template.\n * It can be necessary to raise this value from the default of 1\n * for example when sharing the instance profile with a docker container running on the instance.\n */\n instanceMetadataHopLimit?: number;\n\n /**\n * Specify an update policy for the ASG created by this pattern.\n *\n * @see https://docs.aws.amazon.com/cdk/api/latest/docs/aws-autoscaling-readme.html#update-policy\n *\n * @defaultValue UpdatePolicy.none() - Cloudformation does not attempt to rotate instances in the ASG\n * and must rely on riffraff to do so.\n */\n updatePolicy?: UpdatePolicy;\n\n /**\n * This setting configures the launch template to enable or disable detailed monitoring on instances.\n *\n * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-monitoring.html\n */\n enabledDetailedInstanceMonitoring?: boolean;\n /**\n * You can specify how long after an instance reaches the InService state it waits before contributing\n * usage data to the aggregated metrics. This specified time is called the default instance warmup.\n * This keeps dynamic scaling from being affected by metrics for individual instances that aren't yet\n * handling application traffic and that might be experiencing temporarily high usage of compute resources.\n *\n * @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-default-instance-warmup.html\n */\n defaultInstanceWarmup?: Duration;\n}\n\nfunction restrictedCidrRanges(ranges: IPeer[]) {\n return ranges.map((range) => ({\n range,\n port: Port.tcp(443),\n description: `Allow access on port 443 from ${range.uniqueId}`,\n }));\n}\n\n/**\n * Pattern which creates the resources needed to run an application on EC2\n * behind a load balancer. For convenience, you may wish to use [[`GuPlayApp`]]\n * or [[`GuNodeApp`]], which extend this class.\n *\n * See props for further details.\n */\nexport class GuEc2App extends Construct {\n /*\n * These are public for now, as this allows users to\n * modify these constructs as desired to fit their\n * specific needs.\n * In the future, we might build functionality to better enable users\n * to access in-pattern constructs, but this would allow teams to be unblocked\n * in the short term.\n * */\n public readonly vpc: IVpc;\n public readonly certificate?: GuCertificate;\n public readonly loadBalancer: GuApplicationLoadBalancer;\n public readonly autoScalingGroup: GuAutoScalingGroup;\n public readonly listener: GuHttpsApplicationListener;\n public readonly targetGroup: GuApplicationTargetGroup;\n\n constructor(scope: GuStack, props: GuEc2AppProps) {\n const {\n access,\n accessLogging = { enabled: false },\n app,\n // We should update this default once a significant number of apps have migrated to devx-logs\n applicationLogging = { enabled: false },\n applicationPort,\n blockDevices,\n certificateProps,\n instanceType,\n monitoringConfiguration,\n roleConfiguration = { withoutLogShipping: false, additionalPolicies: [] },\n scaling: { minimumInstances, maximumInstances = minimumInstances * 2 },\n userData: userDataLike,\n imageRecipe,\n vpc = GuVpc.fromIdParameter(scope, AppIdentity.suffixText({ app }, \"VPC\")),\n privateSubnets = GuVpc.subnetsFromParameter(scope, { type: SubnetType.PRIVATE, app }),\n publicSubnets = GuVpc.subnetsFromParameter(scope, { type: SubnetType.PUBLIC, app }),\n instanceMetadataHopLimit,\n updatePolicy,\n enabledDetailedInstanceMonitoring,\n defaultInstanceWarmup,\n } = props;\n\n super(scope, app); // The assumption is `app` is unique\n\n // We should really prevent users from doing this via the type system,\n // but that requires a breaking change to the API\n if (applicationLogging.enabled && roleConfiguration.withoutLogShipping) {\n throw new Error(\n \"Application logging has been enabled (via the `applicationLogging` prop) but your `roleConfiguration` sets \" +\n \"`withoutLogShipping` to true. Please turn off application logging or remove `withoutLogShipping`\",\n );\n }\n\n const userData = userDataLike instanceof UserData ? userDataLike : new GuUserData(scope, { ...userDataLike, app });\n\n AppAccess.validate(access);\n\n const certificate =\n typeof certificateProps !== \"undefined\"\n ? new GuCertificate(scope, {\n app,\n domainName: certificateProps.domainName,\n hostedZoneId: certificateProps.hostedZoneId,\n })\n : undefined;\n\n const maybePrivateConfigPolicy =\n userData instanceof GuUserData && userData.configuration\n ? [new GuGetPrivateConfigPolicy(scope, \"GetPrivateConfigFromS3Policy\", userData.configuration)]\n : [];\n\n const mergedRoleConfiguration: GuInstanceRoleProps = {\n withoutLogShipping: roleConfiguration.withoutLogShipping,\n additionalPolicies: maybePrivateConfigPolicy.concat(roleConfiguration.additionalPolicies ?? []),\n };\n\n const autoScalingGroup = new GuAutoScalingGroup(scope, \"AutoScalingGroup\", {\n app,\n vpc,\n instanceType,\n minimumInstances,\n maximumInstances,\n role: new GuInstanceRole(scope, { app, ...mergedRoleConfiguration }),\n\n // TODO should this be defaulted at pattern or construct level?\n healthChecks: HealthChecks.withAdditionalChecks({\n additionalTypes: [AdditionalHealthCheckType.ELB],\n gracePeriod: Duration.minutes(2),\n }),\n\n userData: userData instanceof GuUserData ? userData.userData : userData,\n vpcSubnets: { subnets: privateSubnets },\n ...(blockDevices && { blockDevices }),\n imageRecipe,\n httpPutResponseHopLimit: instanceMetadataHopLimit,\n updatePolicy,\n enabledDetailedInstanceMonitoring,\n defaultInstanceWarmup,\n });\n\n // This allows automatic shipping of instance Cloud Init logs when using the\n // `cdk-base` Amigo role on your AMI.\n Tags.of(autoScalingGroup).add(\n MetadataKeys.LOG_KINESIS_STREAM_NAME,\n GuLoggingStreamNameParameter.getInstance(scope).valueAsString,\n );\n\n if (applicationLogging.enabled) {\n // This allows automatic shipping of application logs when using the\n // `cdk-base` Amigo role on your AMI.\n Tags.of(autoScalingGroup).add(\n MetadataKeys.SYSTEMD_UNIT,\n applicationLogging.systemdUnitName ? `${applicationLogging.systemdUnitName}.service` : `${app}.service`,\n );\n }\n\n const loadBalancer = new GuApplicationLoadBalancer(scope, \"LoadBalancer\", {\n app,\n vpc,\n // Setting internetFacing to true does not necessarily allow public access to the load balancer itself. That is handled by the listener's `open` prop.\n internetFacing: access.scope !== AccessScope.INTERNAL,\n vpcSubnets: {\n subnets: access.scope === AccessScope.INTERNAL ? privateSubnets : publicSubnets,\n },\n });\n\n if (accessLogging.enabled) {\n const accessLoggingBucket = GuAccessLoggingBucketParameter.getInstance(scope);\n\n loadBalancer.logAccessLogs(\n Bucket.fromBucketName(\n scope,\n AppIdentity.suffixText(props, \"AccessLoggingBucket\"),\n accessLoggingBucket.valueAsString,\n ),\n accessLogging.prefix,\n );\n }\n\n const targetGroup = new GuApplicationTargetGroup(scope, \"TargetGroup\", {\n app,\n vpc,\n protocol: ApplicationProtocol.HTTP,\n targets: [autoScalingGroup],\n port: applicationPort,\n healthCheck: props.healthcheck,\n });\n\n const listener = new GuHttpsApplicationListener(scope, \"Listener\", {\n app,\n loadBalancer,\n certificate,\n targetGroup,\n // When open=true, AWS will create a security group which allows all inbound traffic over HTTPS\n open: access.scope === AccessScope.PUBLIC && typeof certificate !== \"undefined\",\n });\n\n // Since AWS won't create a security group automatically when open=false, we need to add our own\n if (access.scope !== AccessScope.PUBLIC) {\n loadBalancer.addSecurityGroup(\n new GuSecurityGroup(scope, `${access.scope}IngressSecurityGroup`, {\n app,\n vpc,\n description: \"Allow restricted ingress from CIDR ranges\",\n allowAllOutbound: false,\n ingresses: restrictedCidrRanges(access.cidrRanges),\n }),\n );\n }\n\n if (!monitoringConfiguration.noMonitoring) {\n const { http5xxAlarm, http4xxAlarm, snsTopicName, unhealthyInstancesAlarm } = monitoringConfiguration;\n\n if (http4xxAlarm) {\n new GuAlb4xxPercentageAlarm(scope, {\n app,\n loadBalancer,\n snsTopicName,\n ...http4xxAlarm,\n });\n }\n if (http5xxAlarm) {\n new GuAlb5xxPercentageAlarm(scope, {\n app,\n loadBalancer,\n snsTopicName,\n ...http5xxAlarm,\n });\n }\n if (unhealthyInstancesAlarm) {\n new GuUnhealthyInstancesAlarm(scope, {\n app,\n targetGroup,\n snsTopicName,\n });\n }\n }\n\n if (props.googleAuth?.enabled) {\n const prefix = `/${scope.stage}/${scope.stack}/${app}`;\n\n const {\n allowedGroups = [\"engineering@guardian.co.uk\"],\n sessionTimeoutInMinutes = 15,\n credentialsSecretsManagerPath = `${prefix}/google-auth-credentials`,\n } = props.googleAuth;\n\n if (sessionTimeoutInMinutes > 60) {\n throw new Error(\"googleAuth.sessionTimeoutInMinutes must be <= 60!\");\n }\n\n if (allowedGroups.length < 1) {\n throw new Error(\"googleAuth.allowedGroups cannot be empty!\");\n }\n\n if (allowedGroups.find((group) => !group.endsWith(\"@guardian.co.uk\"))) {\n throw new Error(\"googleAuth.allowedGroups must use the @guardian.co.uk domain.\");\n }\n\n const deployToolsAccountId = StringParameter.fromStringParameterName(\n scope,\n \"deploy-tools-account-id-parameter\",\n NAMED_SSM_PARAMETER_PATHS.DeployToolsAccountId.path,\n );\n\n const cognitoAuthStage = props.googleAuth.cognitoAuthStage ?? \"PROD\";\n\n // See https://github.com/guardian/cognito-auth-lambdas for the source\n // code here. ARN format is:\n // arn:aws:lambda:aws-region:acct-id:function:helloworld.\n const gatekeeperFunctionArn = `arn:aws:lambda:eu-west-1:${deployToolsAccountId.stringValue}:function:deploy-${cognitoAuthStage}-gatekeeper-lambda`;\n\n // Note, handler and filename must match here:\n // https://github.com/guardian/cognito-auth-lambdas.\n const authLambda = new GuLambdaFunction(scope, \"auth-lambda\", {\n app: app,\n memorySize: 128,\n handler: \"bootstrap\",\n runtime: Runtime.PROVIDED_AL2,\n fileName: `deploy/${cognitoAuthStage}/cognito-lambda/devx-cognito-lambda-amd64-v2.zip`,\n withoutFilePrefix: true,\n withoutArtifactUpload: true,\n bucketNamePath: NAMED_SSM_PARAMETER_PATHS.OrganisationDistributionBucket.path,\n architecture: Architecture.X86_64,\n environment: {\n ALLOWED_GROUPS: allowedGroups.join(\",\"),\n GATEKEEPER_FUNCTION_ARN: gatekeeperFunctionArn,\n },\n });\n\n authLambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"lambda:InvokeFunction\"],\n resources: [gatekeeperFunctionArn],\n }),\n );\n\n // Cognito user pool. We require both lambdas: pre-sign-up runs the first\n // time a user attempts to authenticate (before they exist in the User\n // Pool); pre-auth runs in subsequent authentication flows.\n const userPool = new UserPool(this, \"user-pool\", {\n lambdaTriggers: {\n preAuthentication: authLambda,\n preSignUp: authLambda,\n },\n });\n\n // These help ensure domain is deterministic but also unique. Key\n // assumption is that app/stack/stage combo are unique within Guardian.\n const domainPrefix = `com-gu-${app.toLowerCase()}-${scope.stage.toLowerCase()}`;\n\n const userPoolDomain = userPool.addDomain(\"domain\", {\n cognitoDomain: {\n domainPrefix: getUserPoolDomainPrefix(domainPrefix),\n },\n });\n\n const clientId = SecretValue.secretsManager(credentialsSecretsManagerPath, { jsonField: \"clientId\" });\n const clientSecret = SecretValue.secretsManager(credentialsSecretsManagerPath, { jsonField: \"clientSecret\" });\n\n const userPoolIdp = new UserPoolIdentityProviderGoogle(scope, \"google-idp\", {\n userPool: userPool,\n clientId: clientId.toString(),\n clientSecretValue: clientSecret,\n attributeMapping: {\n email: ProviderAttribute.GOOGLE_EMAIL,\n givenName: ProviderAttribute.GOOGLE_GIVEN_NAME,\n familyName: ProviderAttribute.GOOGLE_FAMILY_NAME,\n profilePicture: ProviderAttribute.GOOGLE_PICTURE,\n custom: {\n name: ProviderAttribute.GOOGLE_NAME,\n },\n },\n scopes: [\"openid\", \"email\", \"profile\"],\n });\n\n const userPoolClient = userPool.addClient(\"alb-client\", {\n supportedIdentityProviders: [UserPoolClientIdentityProvider.GOOGLE],\n generateSecret: true,\n oAuth: {\n callbackUrls: [`https://${props.googleAuth.domain}/oauth2/idpresponse`],\n },\n\n // Note: id and access validity token validity cannot be less than one\n // hour (this is the cognito cookie duration). To quickly invalidate\n // credentials, disable the user in Cognito. It might be that we want to\n // parameterise these going forward, but that would require Infosec\n // discussion.\n idTokenValidity: Duration.hours(1),\n accessTokenValidity: Duration.hours(1),\n refreshTokenValidity: Duration.days(7),\n });\n\n userPoolClient.node.addDependency(userPoolIdp);\n\n listener.addAction(\"CognitoAuth\", {\n action: new AuthenticateCognitoAction({\n userPool: userPool,\n userPoolClient: userPoolClient,\n userPoolDomain: userPoolDomain,\n next: ListenerAction.forward([targetGroup]),\n sessionTimeout: Duration.minutes(sessionTimeoutInMinutes),\n }),\n });\n\n // Need to give the ALB outbound access on 443 for the IdP endpoints.\n const idpEgressSecurityGroup = new GuHttpsEgressSecurityGroup(scope, \"ldp-access\", {\n app,\n vpc,\n });\n\n loadBalancer.addSecurityGroup(idpEgressSecurityGroup);\n }\n\n this.vpc = vpc;\n this.certificate = certificate;\n this.loadBalancer = loadBalancer;\n this.autoScalingGroup = autoScalingGroup;\n this.listener = listener;\n this.targetGroup = targetGroup;\n }\n}\n"]}
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/patterns/ec2-app/base.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint \"@guardian/tsdoc-required/tsdoc-required\": 2 -- to begin rolling this out for public APIs. */\nimport { Duration, SecretValue, Tags } from \"aws-cdk-lib\";\nimport type { BlockDevice, UpdatePolicy } from \"aws-cdk-lib/aws-autoscaling\";\nimport { AdditionalHealthCheckType, HealthChecks } from \"aws-cdk-lib/aws-autoscaling\";\nimport {\n ProviderAttribute,\n UserPool,\n UserPoolClientIdentityProvider,\n UserPoolIdentityProviderGoogle,\n} from \"aws-cdk-lib/aws-cognito\";\nimport type { InstanceType, IPeer, ISubnet, IVpc } from \"aws-cdk-lib/aws-ec2\";\nimport { Port, UserData } from \"aws-cdk-lib/aws-ec2\";\nimport type { HealthCheck as ALBHealthCheck } from \"aws-cdk-lib/aws-elasticloadbalancingv2\";\nimport { ApplicationProtocol, ListenerAction } from \"aws-cdk-lib/aws-elasticloadbalancingv2\";\nimport { AuthenticateCognitoAction } from \"aws-cdk-lib/aws-elasticloadbalancingv2-actions\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Architecture, Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { Bucket } from \"aws-cdk-lib/aws-s3\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport { AccessScope, MetadataKeys, NAMED_SSM_PARAMETER_PATHS } from \"../../constants\";\nimport { GuCertificate } from \"../../constructs/acm\";\nimport type { GuUserDataProps } from \"../../constructs/autoscaling\";\nimport { GuAutoScalingGroup, GuUserData } from \"../../constructs/autoscaling\";\nimport type { Http4xxAlarmProps, Http5xxAlarmProps, NoMonitoring } from \"../../constructs/cloudwatch\";\nimport {\n GuAlb4xxPercentageAlarm,\n GuAlb5xxPercentageAlarm,\n GuUnhealthyInstancesAlarm,\n} from \"../../constructs/cloudwatch\";\nimport type { GuStack } from \"../../constructs/core\";\nimport { AppIdentity, GuAccessLoggingBucketParameter, GuLoggingStreamNameParameter } from \"../../constructs/core\";\nimport { GuHttpsEgressSecurityGroup, GuSecurityGroup, GuVpc, SubnetType } from \"../../constructs/ec2\";\nimport type { GuInstanceRoleProps } from \"../../constructs/iam\";\nimport { GuGetPrivateConfigPolicy, GuInstanceRole } from \"../../constructs/iam\";\nimport { GuLambdaFunction } from \"../../constructs/lambda\";\nimport {\n GuApplicationLoadBalancer,\n GuApplicationTargetGroup,\n GuHttpsApplicationListener,\n} from \"../../constructs/loadbalancing\";\nimport { AppAccess } from \"../../types\";\nimport type { GuAsgCapacity, GuDomainName } from \"../../types\";\nimport type { AmigoProps } from \"../../types/amigo\";\nimport { getUserPoolDomainPrefix } from \"../../utils/cognito/cognito\";\n\nexport interface AccessLoggingProps {\n /**\n * Enable (load balancer) access logs.\n *\n * Note, you will need to specify a region in your stack declaration to use\n * this.\n * See`https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticloadbalancingv2.ApplicationLoadBalancer.html#logwbraccesswbrlogsbucket-prefix`\n */\n enabled: boolean;\n /**\n * S3 prefix for the logs.\n *\n * @defaultValue no prefix\n */\n prefix?: string;\n}\n\n/**\n * To ship your application logs to ELK automatically, you must:\n *\n * 1. Set the `enabled` flag to true\n * 2. Include the `cdk-base` Amigo role in your AMI\n * 3. Log to `journald`. We recommend doing this by logging to `stdout` and\n * using `systemd` to start your app\n * 4. Confirm that your [[`systemdUnitName`]] is configured properly.\n *\n * Unless you have explicitly opted-out, appropriate IAM permissions for logging\n * to Kinesis will be configured automatically via the [[`GuEc2App`]] pattern.\n */\nexport interface ApplicationLoggingProps {\n /**\n * Enables forwarding of application logs to the Guardian ELK stack.\n *\n * Note, to work, you will need to also do the following non-CDK things:\n *\n * 1. Include the `cdk-base` Amigo role in your AMI.\n * 2. Log to `journald`. We recommend doing this by logging to `stdout` and\n * using `systemd` to start your app\n * 3. Confirm that your [[`systemdUnitName`]] is configured properly.\n *\n * Unless you have explicitly opted-out, appropriate IAM permissions for\n * logging to Kinesis will be configured automatically via the [[`GuEc2App`]]\n * pattern.\n *\n * @see https://github.com/guardian/amigo/tree/main/roles/cdk-base\n */\n enabled: boolean;\n /**\n * This needs to match the name of your SystemD unit.\n *\n * If your systemd unit is not `<app>.service` set this value.\n *\n * @defaultValue `<app>.service`\n */\n systemdUnitName?: string;\n}\n\n/**\n * @privateRemarks we should rename this to AlarmsProps.\n */\nexport interface Alarms {\n /**\n * Name of the target (SNS Topic) for alarm notifications.\n */\n snsTopicName: string;\n /**\n * Enable the 5xx alarm with settings.\n */\n http5xxAlarm: false | Http5xxAlarmProps;\n /**\n * Enable the 4xx alarm with settings.\n */\n http4xxAlarm?: false | Http4xxAlarmProps;\n /**\n * Enable the unhealthy instances alarm.\n */\n unhealthyInstancesAlarm: boolean;\n /**\n * Internal flag - users of this library should ignore this setting.\n */\n noMonitoring?: false;\n}\n\nexport interface GuEc2AppProps extends AppIdentity {\n /**\n * User data for the autoscaling group.\n */\n userData: GuUserDataProps | UserData;\n /**\n * Network access restrictions for your load balancer.\n *\n * Note, this merely provides defence in depth; you should, for example, limit\n * access to the VPN and then treat that as sufficient. Instead, use Google\n * Auth for human access, or a suitable machine auth mechanism.\n */\n access: AppAccess;\n /**\n * The port your application runs on.\n */\n applicationPort: number;\n /**\n * Configure IAM roles for autoscaling group EC2 instances.\n */\n roleConfiguration?: GuInstanceRoleProps;\n /**\n * Enable and configure alarms.\n */\n monitoringConfiguration: Alarms | NoMonitoring;\n /**\n * EC2 instance type. Note, ensure your code is built for the same\n * architecture family (arm64 - 'Graviton' instances - or x64).\n */\n instanceType: InstanceType;\n /**\n * Enable and configures application logs.\n */\n applicationLogging?: ApplicationLoggingProps;\n /**\n * Enable and configures access logs.\n */\n accessLogging?: AccessLoggingProps;\n /**\n * Add block devices (additional storage).\n */\n blockDevices?: BlockDevice[];\n /**\n * Autoscaling group min and max sizes.\n */\n scaling: GuAsgCapacity;\n /**\n * Specify certificate for the load balancer.\n */\n certificateProps?: GuDomainName;\n\n /**\n * Configure AMIgo image recipe. This is only necessary if you are using GuCDK to generate your riff-raff.yaml file.\n */\n imageRecipe?: string | AmigoProps;\n /**\n * Specify the VPC to use.\n *\n * @see https://github.com/guardian/aws-account-setup\n */\n vpc?: IVpc;\n /**\n * Specify private subnets if using a non-default VPC or (generally\n * discouraged) to limit to a subset of the available subnets.\n */\n privateSubnets?: ISubnet[];\n\n /**\n * Specify private subnets if using a non-default VPC or (generally\n * discouraged) to limit to a subset of the available subnets.\n */\n publicSubnets?: ISubnet[];\n\n /**\n * Configure Google Auth.\n */\n googleAuth?: {\n /**\n * Enables Google Auth (via Cognito). **Additional MANUAL steps required -\n * see below.**\n *\n * Limits access to members of the allowed Google groups.\n *\n * Note, this does not currently support simultaneous machine access, so\n * only set to true if you only require staff access to your service, or are\n * supporting machine access in some other way.\n *\n * MANUAL STEPS: to get this to work, we need a Google Project and\n * associated credentials. Full instructions can be found here:\n *\n * https://docs.google.com/document/d/1_k1FSE52AZHXufWLTiKTI3xy5cGpziyHazSHTKrYfco/edit?usp=sharing\n *\n * DevX hope to automate this process in the near future.\n */\n enabled: true;\n /**\n * The domain users will access your service.\n *\n * Set this to the same as for certificateProps.\n */\n domain: string;\n /**\n * Groups used for membership checks.\n *\n * If specified, cannot be empty. Users must be a member of at least one\n * group to gain access.\n *\n * WARNING: groups must be specified with the `guardian.co.uk` domain, even\n * if that is the non-idiomatic choice for daily use.\n *\n * @defaultValue [`engineering@guardian.co.uk`]\n */\n allowedGroups?: string[];\n /**\n * The number of minutes before the session expires.\n *\n * Set this value to a safe period of time that revoked users\n * sessions will continue to function.\n *\n * NOTE: This value cannot be larger than 60 minutes.\n *\n * @defaultValue 15\n */\n sessionTimeoutInMinutes?: number;\n /**\n * Secrets Manager path containing Google OAuth2 Client credentials.\n *\n * NOTE: you do not need to set this value, but you DO need to generate and\n * store the associated credentials in Secrets Manager.\n *\n * Credentials should be stored in Secrets Manager as JSON:\n *\n * ```json\n * {\n * \"clientId\": \"my-client-id\",\n * \"clientSecret\": \"my-client-secret\"\n * }\n * ```\n *\n * @see `googleAuth.enabled` for how to generate.\n *\n * @defaultValue /:STAGE/:stack/:app/google-auth-credentials\n */\n credentialsSecretsManagerPath?: string;\n\n /**\n * When using Auth in the ALB, which stage of cognito-lambda to use.\n *\n * For most applications this should always be PROD, even in the CODE environments.\n *\n * @defaultValue PROD\n */\n cognitoAuthStage?: string;\n };\n\n /**\n * Specify custom healthcheck\n */\n healthcheck?: ALBHealthCheck;\n\n /**\n * Set http put response hop limit for the launch template.\n * It can be necessary to raise this value from the default of 1\n * for example when sharing the instance profile with a docker container running on the instance.\n */\n instanceMetadataHopLimit?: number;\n\n /**\n * Specify an update policy for the ASG created by this pattern.\n *\n * @see https://docs.aws.amazon.com/cdk/api/latest/docs/aws-autoscaling-readme.html#update-policy\n *\n * @defaultValue UpdatePolicy.none() - Cloudformation does not attempt to rotate instances in the ASG\n * and must rely on riffraff to do so.\n */\n updatePolicy?: UpdatePolicy;\n\n /**\n * You can specify how long after an instance reaches the InService state it waits before contributing\n * usage data to the aggregated metrics. This specified time is called the default instance warmup.\n * This keeps dynamic scaling from being affected by metrics for individual instances that aren't yet\n * handling application traffic and that might be experiencing temporarily high usage of compute resources.\n *\n * @see https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-default-instance-warmup.html\n */\n defaultInstanceWarmup?: Duration;\n\n /**\n * How often to send EC2 metrics, such as CPU usage.\n * By default, AWS will produce `5Minute` granular metrics.\n *\n * It is recommended to produce `1Minute` granular metrics in production,\n * especially when using ASG metrics to trigger horizontal scaling as it allows for earlier scaling.\n *\n * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html\n */\n instanceMetricGranularity: \"1Minute\" | \"5Minute\";\n}\n\nfunction restrictedCidrRanges(ranges: IPeer[]) {\n return ranges.map((range) => ({\n range,\n port: Port.tcp(443),\n description: `Allow access on port 443 from ${range.uniqueId}`,\n }));\n}\n\n/**\n * Pattern which creates the resources needed to run an application on EC2\n * behind a load balancer. For convenience, you may wish to use [[`GuPlayApp`]]\n * or [[`GuNodeApp`]], which extend this class.\n *\n * See props for further details.\n */\nexport class GuEc2App extends Construct {\n /*\n * These are public for now, as this allows users to\n * modify these constructs as desired to fit their\n * specific needs.\n * In the future, we might build functionality to better enable users\n * to access in-pattern constructs, but this would allow teams to be unblocked\n * in the short term.\n * */\n public readonly vpc: IVpc;\n public readonly certificate?: GuCertificate;\n public readonly loadBalancer: GuApplicationLoadBalancer;\n public readonly autoScalingGroup: GuAutoScalingGroup;\n public readonly listener: GuHttpsApplicationListener;\n public readonly targetGroup: GuApplicationTargetGroup;\n\n constructor(scope: GuStack, props: GuEc2AppProps) {\n const {\n access,\n accessLogging = { enabled: false },\n app,\n // We should update this default once a significant number of apps have migrated to devx-logs\n applicationLogging = { enabled: false },\n applicationPort,\n blockDevices,\n certificateProps,\n instanceType,\n monitoringConfiguration,\n roleConfiguration = { withoutLogShipping: false, additionalPolicies: [] },\n scaling: { minimumInstances, maximumInstances = minimumInstances * 2 },\n userData: userDataLike,\n imageRecipe,\n vpc = GuVpc.fromIdParameter(scope, AppIdentity.suffixText({ app }, \"VPC\")),\n privateSubnets = GuVpc.subnetsFromParameter(scope, { type: SubnetType.PRIVATE, app }),\n publicSubnets = GuVpc.subnetsFromParameter(scope, { type: SubnetType.PUBLIC, app }),\n instanceMetadataHopLimit,\n updatePolicy,\n defaultInstanceWarmup,\n instanceMetricGranularity,\n } = props;\n\n super(scope, app); // The assumption is `app` is unique\n\n // We should really prevent users from doing this via the type system,\n // but that requires a breaking change to the API\n if (applicationLogging.enabled && roleConfiguration.withoutLogShipping) {\n throw new Error(\n \"Application logging has been enabled (via the `applicationLogging` prop) but your `roleConfiguration` sets \" +\n \"`withoutLogShipping` to true. Please turn off application logging or remove `withoutLogShipping`\",\n );\n }\n\n const userData = userDataLike instanceof UserData ? userDataLike : new GuUserData(scope, { ...userDataLike, app });\n\n AppAccess.validate(access);\n\n const certificate =\n typeof certificateProps !== \"undefined\"\n ? new GuCertificate(scope, {\n app,\n domainName: certificateProps.domainName,\n hostedZoneId: certificateProps.hostedZoneId,\n })\n : undefined;\n\n const maybePrivateConfigPolicy =\n userData instanceof GuUserData && userData.configuration\n ? [new GuGetPrivateConfigPolicy(scope, \"GetPrivateConfigFromS3Policy\", userData.configuration)]\n : [];\n\n const mergedRoleConfiguration: GuInstanceRoleProps = {\n withoutLogShipping: roleConfiguration.withoutLogShipping,\n additionalPolicies: maybePrivateConfigPolicy.concat(roleConfiguration.additionalPolicies ?? []),\n };\n\n const autoScalingGroup = new GuAutoScalingGroup(scope, \"AutoScalingGroup\", {\n app,\n vpc,\n instanceType,\n minimumInstances,\n maximumInstances,\n role: new GuInstanceRole(scope, { app, ...mergedRoleConfiguration }),\n\n // TODO should this be defaulted at pattern or construct level?\n healthChecks: HealthChecks.withAdditionalChecks({\n additionalTypes: [AdditionalHealthCheckType.ELB],\n gracePeriod: Duration.minutes(2),\n }),\n\n userData: userData instanceof GuUserData ? userData.userData : userData,\n vpcSubnets: { subnets: privateSubnets },\n ...(blockDevices && { blockDevices }),\n imageRecipe,\n httpPutResponseHopLimit: instanceMetadataHopLimit,\n updatePolicy,\n defaultInstanceWarmup,\n instanceMetricGranularity,\n });\n\n // This allows automatic shipping of instance Cloud Init logs when using the\n // `cdk-base` Amigo role on your AMI.\n Tags.of(autoScalingGroup).add(\n MetadataKeys.LOG_KINESIS_STREAM_NAME,\n GuLoggingStreamNameParameter.getInstance(scope).valueAsString,\n );\n\n if (applicationLogging.enabled) {\n // This allows automatic shipping of application logs when using the\n // `cdk-base` Amigo role on your AMI.\n Tags.of(autoScalingGroup).add(\n MetadataKeys.SYSTEMD_UNIT,\n applicationLogging.systemdUnitName ? `${applicationLogging.systemdUnitName}.service` : `${app}.service`,\n );\n }\n\n const loadBalancer = new GuApplicationLoadBalancer(scope, \"LoadBalancer\", {\n app,\n vpc,\n // Setting internetFacing to true does not necessarily allow public access to the load balancer itself. That is handled by the listener's `open` prop.\n internetFacing: access.scope !== AccessScope.INTERNAL,\n vpcSubnets: {\n subnets: access.scope === AccessScope.INTERNAL ? privateSubnets : publicSubnets,\n },\n });\n\n if (accessLogging.enabled) {\n const accessLoggingBucket = GuAccessLoggingBucketParameter.getInstance(scope);\n\n loadBalancer.logAccessLogs(\n Bucket.fromBucketName(\n scope,\n AppIdentity.suffixText(props, \"AccessLoggingBucket\"),\n accessLoggingBucket.valueAsString,\n ),\n accessLogging.prefix,\n );\n }\n\n const targetGroup = new GuApplicationTargetGroup(scope, \"TargetGroup\", {\n app,\n vpc,\n protocol: ApplicationProtocol.HTTP,\n targets: [autoScalingGroup],\n port: applicationPort,\n healthCheck: props.healthcheck,\n });\n\n const listener = new GuHttpsApplicationListener(scope, \"Listener\", {\n app,\n loadBalancer,\n certificate,\n targetGroup,\n // When open=true, AWS will create a security group which allows all inbound traffic over HTTPS\n open: access.scope === AccessScope.PUBLIC && typeof certificate !== \"undefined\",\n });\n\n // Since AWS won't create a security group automatically when open=false, we need to add our own\n if (access.scope !== AccessScope.PUBLIC) {\n loadBalancer.addSecurityGroup(\n new GuSecurityGroup(scope, `${access.scope}IngressSecurityGroup`, {\n app,\n vpc,\n description: \"Allow restricted ingress from CIDR ranges\",\n allowAllOutbound: false,\n ingresses: restrictedCidrRanges(access.cidrRanges),\n }),\n );\n }\n\n if (!monitoringConfiguration.noMonitoring) {\n const { http5xxAlarm, http4xxAlarm, snsTopicName, unhealthyInstancesAlarm } = monitoringConfiguration;\n\n if (http4xxAlarm) {\n new GuAlb4xxPercentageAlarm(scope, {\n app,\n loadBalancer,\n snsTopicName,\n ...http4xxAlarm,\n });\n }\n if (http5xxAlarm) {\n new GuAlb5xxPercentageAlarm(scope, {\n app,\n loadBalancer,\n snsTopicName,\n ...http5xxAlarm,\n });\n }\n if (unhealthyInstancesAlarm) {\n new GuUnhealthyInstancesAlarm(scope, {\n app,\n targetGroup,\n snsTopicName,\n });\n }\n }\n\n if (props.googleAuth?.enabled) {\n const prefix = `/${scope.stage}/${scope.stack}/${app}`;\n\n const {\n allowedGroups = [\"engineering@guardian.co.uk\"],\n sessionTimeoutInMinutes = 15,\n credentialsSecretsManagerPath = `${prefix}/google-auth-credentials`,\n } = props.googleAuth;\n\n if (sessionTimeoutInMinutes > 60) {\n throw new Error(\"googleAuth.sessionTimeoutInMinutes must be <= 60!\");\n }\n\n if (allowedGroups.length < 1) {\n throw new Error(\"googleAuth.allowedGroups cannot be empty!\");\n }\n\n if (allowedGroups.find((group) => !group.endsWith(\"@guardian.co.uk\"))) {\n throw new Error(\"googleAuth.allowedGroups must use the @guardian.co.uk domain.\");\n }\n\n const deployToolsAccountId = StringParameter.fromStringParameterName(\n scope,\n \"deploy-tools-account-id-parameter\",\n NAMED_SSM_PARAMETER_PATHS.DeployToolsAccountId.path,\n );\n\n const cognitoAuthStage = props.googleAuth.cognitoAuthStage ?? \"PROD\";\n\n // See https://github.com/guardian/cognito-auth-lambdas for the source\n // code here. ARN format is:\n // arn:aws:lambda:aws-region:acct-id:function:helloworld.\n const gatekeeperFunctionArn = `arn:aws:lambda:eu-west-1:${deployToolsAccountId.stringValue}:function:deploy-${cognitoAuthStage}-gatekeeper-lambda`;\n\n // Note, handler and filename must match here:\n // https://github.com/guardian/cognito-auth-lambdas.\n const authLambda = new GuLambdaFunction(scope, \"auth-lambda\", {\n app: app,\n memorySize: 128,\n handler: \"bootstrap\",\n runtime: Runtime.PROVIDED_AL2,\n fileName: `deploy/${cognitoAuthStage}/cognito-lambda/devx-cognito-lambda-amd64-v2.zip`,\n withoutFilePrefix: true,\n withoutArtifactUpload: true,\n bucketNamePath: NAMED_SSM_PARAMETER_PATHS.OrganisationDistributionBucket.path,\n architecture: Architecture.X86_64,\n environment: {\n ALLOWED_GROUPS: allowedGroups.join(\",\"),\n GATEKEEPER_FUNCTION_ARN: gatekeeperFunctionArn,\n },\n });\n\n authLambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"lambda:InvokeFunction\"],\n resources: [gatekeeperFunctionArn],\n }),\n );\n\n // Cognito user pool. We require both lambdas: pre-sign-up runs the first\n // time a user attempts to authenticate (before they exist in the User\n // Pool); pre-auth runs in subsequent authentication flows.\n const userPool = new UserPool(this, \"user-pool\", {\n lambdaTriggers: {\n preAuthentication: authLambda,\n preSignUp: authLambda,\n },\n });\n\n // These help ensure domain is deterministic but also unique. Key\n // assumption is that app/stack/stage combo are unique within Guardian.\n const domainPrefix = `com-gu-${app.toLowerCase()}-${scope.stage.toLowerCase()}`;\n\n const userPoolDomain = userPool.addDomain(\"domain\", {\n cognitoDomain: {\n domainPrefix: getUserPoolDomainPrefix(domainPrefix),\n },\n });\n\n const clientId = SecretValue.secretsManager(credentialsSecretsManagerPath, { jsonField: \"clientId\" });\n const clientSecret = SecretValue.secretsManager(credentialsSecretsManagerPath, { jsonField: \"clientSecret\" });\n\n const userPoolIdp = new UserPoolIdentityProviderGoogle(scope, \"google-idp\", {\n userPool: userPool,\n clientId: clientId.toString(),\n clientSecretValue: clientSecret,\n attributeMapping: {\n email: ProviderAttribute.GOOGLE_EMAIL,\n givenName: ProviderAttribute.GOOGLE_GIVEN_NAME,\n familyName: ProviderAttribute.GOOGLE_FAMILY_NAME,\n profilePicture: ProviderAttribute.GOOGLE_PICTURE,\n custom: {\n name: ProviderAttribute.GOOGLE_NAME,\n },\n },\n scopes: [\"openid\", \"email\", \"profile\"],\n });\n\n const userPoolClient = userPool.addClient(\"alb-client\", {\n supportedIdentityProviders: [UserPoolClientIdentityProvider.GOOGLE],\n generateSecret: true,\n oAuth: {\n callbackUrls: [`https://${props.googleAuth.domain}/oauth2/idpresponse`],\n },\n\n // Note: id and access validity token validity cannot be less than one\n // hour (this is the cognito cookie duration). To quickly invalidate\n // credentials, disable the user in Cognito. It might be that we want to\n // parameterise these going forward, but that would require Infosec\n // discussion.\n idTokenValidity: Duration.hours(1),\n accessTokenValidity: Duration.hours(1),\n refreshTokenValidity: Duration.days(7),\n });\n\n userPoolClient.node.addDependency(userPoolIdp);\n\n listener.addAction(\"CognitoAuth\", {\n action: new AuthenticateCognitoAction({\n userPool: userPool,\n userPoolClient: userPoolClient,\n userPoolDomain: userPoolDomain,\n next: ListenerAction.forward([targetGroup]),\n sessionTimeout: Duration.minutes(sessionTimeoutInMinutes),\n }),\n });\n\n // Need to give the ALB outbound access on 443 for the IdP endpoints.\n const idpEgressSecurityGroup = new GuHttpsEgressSecurityGroup(scope, \"ldp-access\", {\n app,\n vpc,\n });\n\n loadBalancer.addSecurityGroup(idpEgressSecurityGroup);\n }\n\n this.vpc = vpc;\n this.certificate = certificate;\n this.loadBalancer = loadBalancer;\n this.autoScalingGroup = autoScalingGroup;\n this.listener = listener;\n this.targetGroup = targetGroup;\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/riff-raff-yaml-file/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAgBvC,OAAO,KAAK,EAMV,YAAY,EAGb,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAY;IACzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAa;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAa;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAW;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC;;;;;;;;;;OAUG;IACH,SAAgB,YAAY,EAAE,YAAY,CAAC;IAE3C,OAAO,CAAC,iBAAiB;IASzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqDG;IACH,OAAO,CAAC,mBAAmB;IAiC3B,OAAO,CAAC,6BAA6B;IAQrC,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,sBAAsB;gBAKlB,GAAG,EAAE,GAAG;IA+FpB;;;OAGG;IACH,MAAM,IAAI,MAAM;IAiBhB;;;OAGG;IACH,KAAK,IAAI,IAAI;CAId"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/riff-raff-yaml-file/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAgBvC,OAAO,KAAK,EAMV,YAAY,EAGb,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAY;IACzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAa;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAa;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAW;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC;;;;;;;;;;OAUG;IACH,SAAgB,YAAY,EAAE,YAAY,CAAC;IAE3C,OAAO,CAAC,iBAAiB;IASzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqDG;IACH,OAAO,CAAC,mBAAmB;IAiC3B,OAAO,CAAC,6BAA6B;IAQrC,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,sBAAsB;gBAKlB,GAAG,EAAE,GAAG;IAgGpB;;;OAGG;IACH,MAAM,IAAI,MAAM;IAiBhB;;;OAGG;IACH,KAAK,IAAI,IAAI;CAId"}
@@ -211,7 +211,7 @@ class RiffRaffYamlFile {
211
211
  deployments.set(asgDeployment.name, asgDeployment.props);
212
212
  });
213
213
  const amiParametersToTags = (0, cloudformation_1.getAmiParameters)(autoscalingGroups);
214
- const minInServiceParamMap = ec2_app_1.HorizontallyScalingDeploymentProperties.getInstance(stack).asgToParamMap;
214
+ const minInServiceParamMap = ec2_app_1.GuHorizontallyScalingDeploymentPropertiesExperimental.getInstance(stack).asgToParamMap;
215
215
  const minInServiceAsgs = autoscalingGroups.filter((asg) => minInServiceParamMap.has(asg.node.id));
216
216
  const minInstancesInServiceParameters = (0, cloudformation_1.getMinInstancesInServiceParameters)(minInServiceAsgs);
217
217
  deployments.set(cfnDeployment.name, {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/riff-raff-yaml-file/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeFileSync } from \"fs\";\nimport path from \"path\";\nimport type { App } from \"aws-cdk-lib\";\nimport { Token } from \"aws-cdk-lib\";\nimport type { CfnAutoScalingGroup } from \"aws-cdk-lib/aws-autoscaling\";\nimport { dump } from \"js-yaml\";\nimport { GuAutoScalingGroup } from \"../constructs/autoscaling\";\nimport { GuStack } from \"../constructs/core\";\nimport { GuLambdaFunction } from \"../constructs/lambda\";\nimport { HorizontallyScalingDeploymentProperties } from \"../experimental/patterns/ec2-app\";\nimport { autoscalingDeployment, uploadAutoscalingArtifact } from \"./deployments/autoscaling\";\nimport {\n cloudFormationDeployment,\n getAmiParameters,\n getMinInstancesInServiceParameters,\n} from \"./deployments/cloudformation\";\nimport { updateLambdaDeployment, uploadLambdaArtifact } from \"./deployments/lambda\";\nimport { groupByClassNameStackRegionStage } from \"./group-by\";\nimport type {\n GroupedCdkStacks,\n Region,\n RiffRaffDeployment,\n RiffRaffDeploymentName,\n RiffRaffDeploymentProps,\n RiffRaffYaml,\n StackTag,\n StageTag,\n} from \"./types\";\n\n/**\n * A class that creates a `riff-raff.yaml` file.\n *\n * Rather than using this directly, prefer to use [[`GuRoot`]] instead.\n *\n * Supports:\n * - Multiple CloudFormation stacks\n * - Multiple regions\n * - Lambda applications\n * - EC2 (autoscaling) applications\n *\n * For lambda applications, 3 deployments will be defined:\n * 1. Lambda upload (`aws-lambda`, `action: [uploadLambda]`)\n * 2. CloudFormation deploy (`cloud-formation`)\n * 3. Lambda update (`aws-lambda`, `action: [updateLambda]`)\n *\n * For EC2 applications, 3 deployments will be defined:\n * 1. Artifact upload (`autoscaling`, `action: [uploadArtifacts]`)\n * 2. CloudFormation deploy (`cloud-formation`)\n * 3. Autoscaling deploy (`autoscaling`, `action: [deploy]`)\n *\n * It assumes a Riff-Raff bundle structure as follows:\n *\n * ```\n * .\n * ├── cdk.out\n * │ └── MyApplication.template.json\n * ├── my-application\n * │ └── my-application.deb\n * └── my-lambda\n * └── my-lambda.zip\n * ```\n *\n * That is, all CloudFormation templates are in a `cdk.out` directory, and there is a directory per app.\n * Application artifact(s) are in the app directory.\n *\n * NOTE: The file extension is decided by you, the above file tree is used for illustration purposes.\n *\n * NOTE: Resources will be looked up by tags (Stack, Stage, App). Ensure your CFN stack is tagged appropriately!\n *\n * @see https://riffraff.gutools.co.uk/docs/reference/riff-raff.yaml.md\n * @see https://riffraff.gutools.co.uk/docs/magenta-lib/types\n */\nexport class RiffRaffYamlFile {\n private readonly allCdkStacks: GuStack[];\n private readonly allStackTags: StackTag[];\n private readonly allStageTags: StageTag[];\n private readonly allRegions: Region[];\n private readonly outdir: string;\n\n /**\n * The `riff-raff.yaml` file as an object.\n *\n * It is useful for specifying additional deployment types that GuCDK does not support.\n * Consider raising an issue or pull request if you think it should be supported.\n *\n * In most cases, you shouldn't need to access this.\n * No validation is performed on the parameters you provide, so you might get deployment errors.\n *\n * @see https://riffraff.gutools.co.uk/docs/magenta-lib/types\n */\n public readonly riffRaffYaml: RiffRaffYaml;\n\n private isCdkStackPresent(expectedStack: StackTag, expectedStage: StageTag): boolean {\n const matches = this.allCdkStacks.find((cdkStack) => {\n const { stack, stage } = cdkStack;\n return stack === expectedStack && stage === expectedStage;\n });\n\n return !!matches;\n }\n\n /**\n * Check there are the appropriate number of `GuStack`s.\n * Expect to find an instance for each combination of `stack`, and `stage`.\n *\n * If not valid, a message is logged describing what is missing to aid debugging.\n *\n * Given the following:\n *\n * ```ts\n * const app = new App();\n *\n * class MyApplicationStack extends GuStack { }\n *\n * new MyApplicationStack(app, \"App-CODE-deploy\", {\n * env: {\n * region: \"eu-west-1\",\n * },\n * stack: \"deploy\",\n * stage: \"CODE\"\n * });\n *\n * new MyApplicationStack(app, \"App-PROD-media-service\", {\n * env: {\n * region: \"eu-west-1\",\n * },\n * stack: \"media-service\",\n * stage: \"PROD\",\n * });\n *\n * new MyApplicationStack(app, \"App-PROD-deploy\", {\n * env: {\n * region: \"eu-west-1\",\n * },\n * stack: \"deploy\",\n * stage: \"PROD\"\n * });\n * ```\n *\n * This will log a message like this, where ❌ denotes something missing,\n * specifically there is no `CODE` template for `media-service`.\n *\n * ```log\n * Unable to produce a working riff-raff.yaml file; missing 1 definitions (details below)\n *\n * ┌───────────────┬──────┬──────┐\n * │ (index) │ CODE │ PROD │\n * ├───────────────┼──────┼──────┤\n * │ deploy │ '✅' │ '✅' │\n * │ media-service │ '❌' │ '✅' │\n * └───────────────┴──────┴──────┘\n * ```\n *\n * @private\n */\n private validateStacksInApp(): void {\n type Found = \"✅\";\n type NotFound = \"❌\";\n type AppValidation = Record<StackTag, Record<StageTag, Found | NotFound>>;\n\n const { allStackTags, allStageTags } = this;\n\n const checks: AppValidation = allStackTags.reduce((accStackTag, stackTag) => {\n return {\n ...accStackTag,\n [stackTag]: allStageTags.reduce((accStageTag, stageTag) => {\n return {\n ...accStageTag,\n [stageTag]: this.isCdkStackPresent(stackTag, stageTag) ? \"✅\" : \"❌\",\n };\n }, {}),\n };\n }, {});\n\n const missingDefinitions: Array<Found | NotFound> = Object.values(checks).flatMap((groupedByStackTag) => {\n return Object.values(groupedByStackTag).filter((_) => _ === \"❌\");\n });\n\n if (missingDefinitions.length > 0) {\n const message = `Unable to produce a working riff-raff.yaml file; missing ${missingDefinitions.length} definitions`;\n\n console.log(`${message} (details below)`);\n console.table(checks);\n\n throw new Error(message);\n }\n }\n\n private validateAllRegionsAreResolved(): void {\n const unresolved = this.allRegions.filter((region) => Token.isUnresolved(region));\n\n if (unresolved.length !== 0) {\n throw new Error(`Unable to produce a working riff-raff.yaml file; all stacks must have an explicit region set`);\n }\n }\n\n private getLambdas(cdkStack: GuStack): GuLambdaFunction[] {\n return cdkStack.node.findAll().filter((_) => _ instanceof GuLambdaFunction) as GuLambdaFunction[];\n }\n\n private getAutoScalingGroups(cdkStack: GuStack): GuAutoScalingGroup[] {\n return cdkStack.node.findAll().filter((_) => _ instanceof GuAutoScalingGroup) as GuAutoScalingGroup[];\n }\n\n private getGuStackDependencies(cdkStack: GuStack): GuStack[] {\n return cdkStack.dependencies.filter((_) => _ instanceof GuStack) as GuStack[];\n }\n\n // eslint-disable-next-line custom-rules/valid-constructors -- this needs to sit above GuStack on the cdk tree\n constructor(app: App) {\n this.allCdkStacks = app.node.findAll().filter((_) => _ instanceof GuStack) as GuStack[];\n const allowedStages = new Set(this.allCdkStacks.map(({ stage }) => stage));\n this.allStageTags = Array.from(allowedStages);\n this.allStackTags = Array.from(new Set(this.allCdkStacks.map(({ stack }) => stack)));\n this.allRegions = Array.from(new Set(this.allCdkStacks.map(({ region }) => region)));\n\n this.validateStacksInApp();\n this.validateAllRegionsAreResolved();\n\n this.outdir = app.outdir;\n\n const deployments = new Map<RiffRaffDeploymentName, RiffRaffDeploymentProps>();\n\n const groupedStacks: GroupedCdkStacks = groupByClassNameStackRegionStage(this.allCdkStacks);\n\n Object.values(groupedStacks).forEach((stackTagGroup) => {\n Object.values(stackTagGroup).forEach((regionGroup) => {\n Object.values(regionGroup).forEach((stageGroup) => {\n const stacks: GuStack[] = Object.values(stageGroup).flat();\n\n // The items in `stacks` only differ by stage, so we can just use the first item in the list.\n const [stack] = stacks;\n\n if (!stack) {\n throw new Error(\"Unable to produce a working riff-raff.yaml file; there are no stacks!\");\n }\n\n const lambdas = this.getLambdas(stack);\n const autoscalingGroups = this.getAutoScalingGroups(stack);\n\n const artifactUploads: RiffRaffDeployment[] = [\n lambdas.filter((_) => !_.withoutArtifactUpload).map(uploadLambdaArtifact),\n autoscalingGroups.map(uploadAutoscalingArtifact),\n ].flat();\n artifactUploads.forEach(({ name, props }) => deployments.set(name, props));\n\n const parentStacks: RiffRaffDeployment[] = this.getGuStackDependencies(stack).map((x) =>\n cloudFormationDeployment([x], [], this.outdir),\n );\n\n const cfnDeployment = cloudFormationDeployment(stacks, [...artifactUploads, ...parentStacks], this.outdir);\n deployments.set(cfnDeployment.name, cfnDeployment.props);\n\n const lambdasWithoutAnAlias = lambdas.filter((lambda) => lambda.alias === undefined);\n // If the Lambda has an alias it is using versioning. When using versioning, there is no need for Riff-Raff\n // to modify the unpublished version of the function\n lambdasWithoutAnAlias.forEach((lambda) => {\n const lambdaDeployment = updateLambdaDeployment(lambda, cfnDeployment);\n deployments.set(lambdaDeployment.name, lambdaDeployment.props);\n });\n\n /*\n Instances in an ASG with an `AutoScalingRollingUpdate` update policy are rotated via CloudFormation.\n Therefore, they do not need to also perform an `autoscaling` deployment via Riff-Raff.\n */\n const legacyAutoscalingGroups = autoscalingGroups.filter((asg) => {\n const { cfnOptions } = asg.node.defaultChild as CfnAutoScalingGroup;\n const { updatePolicy } = cfnOptions;\n return updatePolicy?.autoScalingRollingUpdate === undefined;\n });\n\n legacyAutoscalingGroups.forEach((asg) => {\n const asgDeployment = autoscalingDeployment(asg, cfnDeployment);\n deployments.set(asgDeployment.name, asgDeployment.props);\n });\n\n const amiParametersToTags = getAmiParameters(autoscalingGroups);\n\n const minInServiceParamMap = HorizontallyScalingDeploymentProperties.getInstance(stack).asgToParamMap;\n const minInServiceAsgs = autoscalingGroups.filter((asg) => minInServiceParamMap.has(asg.node.id));\n const minInstancesInServiceParameters = getMinInstancesInServiceParameters(minInServiceAsgs);\n\n deployments.set(cfnDeployment.name, {\n ...cfnDeployment.props,\n parameters: {\n ...cfnDeployment.props.parameters,\n\n // only add the `amiParametersToTags` property if there are some\n ...(autoscalingGroups.length > 0 && { amiParametersToTags }),\n\n // only add the `minInstancesInServiceParameters` property if there are some\n ...(minInServiceAsgs.length > 0 && { minInstancesInServiceParameters }),\n },\n });\n });\n });\n });\n\n this.riffRaffYaml = {\n allowedStages,\n deployments,\n };\n }\n\n /**\n * The `riff-raff.yaml` file as a string.\n * Useful for testing.\n */\n toYAML(): string {\n // Add support for ES6 Set and Map. See https://github.com/nodeca/js-yaml/issues/436.\n const replacer = (_key: string, value: unknown) => {\n if (value instanceof Set) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- this is how `js-yaml` is typed\n return Array.from(value);\n }\n if (value instanceof Map) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- this is how `js-yaml` is typed\n return Object.fromEntries(value);\n }\n return value;\n };\n\n return dump(this.riffRaffYaml, { replacer });\n }\n\n /**\n * Write the `riff-raff.yaml` file to disk.\n * It'll be located with the CFN JSON templates generated by `cdk synth`.\n */\n synth(): void {\n const outPath = path.join(this.outdir, \"riff-raff.yaml\");\n writeFileSync(outPath, this.toYAML());\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/riff-raff-yaml-file/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeFileSync } from \"fs\";\nimport path from \"path\";\nimport type { App } from \"aws-cdk-lib\";\nimport { Token } from \"aws-cdk-lib\";\nimport type { CfnAutoScalingGroup } from \"aws-cdk-lib/aws-autoscaling\";\nimport { dump } from \"js-yaml\";\nimport { GuAutoScalingGroup } from \"../constructs/autoscaling\";\nimport { GuStack } from \"../constructs/core\";\nimport { GuLambdaFunction } from \"../constructs/lambda\";\nimport { GuHorizontallyScalingDeploymentPropertiesExperimental } from \"../experimental/patterns/ec2-app\";\nimport { autoscalingDeployment, uploadAutoscalingArtifact } from \"./deployments/autoscaling\";\nimport {\n cloudFormationDeployment,\n getAmiParameters,\n getMinInstancesInServiceParameters,\n} from \"./deployments/cloudformation\";\nimport { updateLambdaDeployment, uploadLambdaArtifact } from \"./deployments/lambda\";\nimport { groupByClassNameStackRegionStage } from \"./group-by\";\nimport type {\n GroupedCdkStacks,\n Region,\n RiffRaffDeployment,\n RiffRaffDeploymentName,\n RiffRaffDeploymentProps,\n RiffRaffYaml,\n StackTag,\n StageTag,\n} from \"./types\";\n\n/**\n * A class that creates a `riff-raff.yaml` file.\n *\n * Rather than using this directly, prefer to use [[`GuRoot`]] instead.\n *\n * Supports:\n * - Multiple CloudFormation stacks\n * - Multiple regions\n * - Lambda applications\n * - EC2 (autoscaling) applications\n *\n * For lambda applications, 3 deployments will be defined:\n * 1. Lambda upload (`aws-lambda`, `action: [uploadLambda]`)\n * 2. CloudFormation deploy (`cloud-formation`)\n * 3. Lambda update (`aws-lambda`, `action: [updateLambda]`)\n *\n * For EC2 applications, 3 deployments will be defined:\n * 1. Artifact upload (`autoscaling`, `action: [uploadArtifacts]`)\n * 2. CloudFormation deploy (`cloud-formation`)\n * 3. Autoscaling deploy (`autoscaling`, `action: [deploy]`)\n *\n * It assumes a Riff-Raff bundle structure as follows:\n *\n * ```\n * .\n * ├── cdk.out\n * │ └── MyApplication.template.json\n * ├── my-application\n * │ └── my-application.deb\n * └── my-lambda\n * └── my-lambda.zip\n * ```\n *\n * That is, all CloudFormation templates are in a `cdk.out` directory, and there is a directory per app.\n * Application artifact(s) are in the app directory.\n *\n * NOTE: The file extension is decided by you, the above file tree is used for illustration purposes.\n *\n * NOTE: Resources will be looked up by tags (Stack, Stage, App). Ensure your CFN stack is tagged appropriately!\n *\n * @see https://riffraff.gutools.co.uk/docs/reference/riff-raff.yaml.md\n * @see https://riffraff.gutools.co.uk/docs/magenta-lib/types\n */\nexport class RiffRaffYamlFile {\n private readonly allCdkStacks: GuStack[];\n private readonly allStackTags: StackTag[];\n private readonly allStageTags: StageTag[];\n private readonly allRegions: Region[];\n private readonly outdir: string;\n\n /**\n * The `riff-raff.yaml` file as an object.\n *\n * It is useful for specifying additional deployment types that GuCDK does not support.\n * Consider raising an issue or pull request if you think it should be supported.\n *\n * In most cases, you shouldn't need to access this.\n * No validation is performed on the parameters you provide, so you might get deployment errors.\n *\n * @see https://riffraff.gutools.co.uk/docs/magenta-lib/types\n */\n public readonly riffRaffYaml: RiffRaffYaml;\n\n private isCdkStackPresent(expectedStack: StackTag, expectedStage: StageTag): boolean {\n const matches = this.allCdkStacks.find((cdkStack) => {\n const { stack, stage } = cdkStack;\n return stack === expectedStack && stage === expectedStage;\n });\n\n return !!matches;\n }\n\n /**\n * Check there are the appropriate number of `GuStack`s.\n * Expect to find an instance for each combination of `stack`, and `stage`.\n *\n * If not valid, a message is logged describing what is missing to aid debugging.\n *\n * Given the following:\n *\n * ```ts\n * const app = new App();\n *\n * class MyApplicationStack extends GuStack { }\n *\n * new MyApplicationStack(app, \"App-CODE-deploy\", {\n * env: {\n * region: \"eu-west-1\",\n * },\n * stack: \"deploy\",\n * stage: \"CODE\"\n * });\n *\n * new MyApplicationStack(app, \"App-PROD-media-service\", {\n * env: {\n * region: \"eu-west-1\",\n * },\n * stack: \"media-service\",\n * stage: \"PROD\",\n * });\n *\n * new MyApplicationStack(app, \"App-PROD-deploy\", {\n * env: {\n * region: \"eu-west-1\",\n * },\n * stack: \"deploy\",\n * stage: \"PROD\"\n * });\n * ```\n *\n * This will log a message like this, where ❌ denotes something missing,\n * specifically there is no `CODE` template for `media-service`.\n *\n * ```log\n * Unable to produce a working riff-raff.yaml file; missing 1 definitions (details below)\n *\n * ┌───────────────┬──────┬──────┐\n * │ (index) │ CODE │ PROD │\n * ├───────────────┼──────┼──────┤\n * │ deploy │ '✅' │ '✅' │\n * │ media-service │ '❌' │ '✅' │\n * └───────────────┴──────┴──────┘\n * ```\n *\n * @private\n */\n private validateStacksInApp(): void {\n type Found = \"✅\";\n type NotFound = \"❌\";\n type AppValidation = Record<StackTag, Record<StageTag, Found | NotFound>>;\n\n const { allStackTags, allStageTags } = this;\n\n const checks: AppValidation = allStackTags.reduce((accStackTag, stackTag) => {\n return {\n ...accStackTag,\n [stackTag]: allStageTags.reduce((accStageTag, stageTag) => {\n return {\n ...accStageTag,\n [stageTag]: this.isCdkStackPresent(stackTag, stageTag) ? \"✅\" : \"❌\",\n };\n }, {}),\n };\n }, {});\n\n const missingDefinitions: Array<Found | NotFound> = Object.values(checks).flatMap((groupedByStackTag) => {\n return Object.values(groupedByStackTag).filter((_) => _ === \"❌\");\n });\n\n if (missingDefinitions.length > 0) {\n const message = `Unable to produce a working riff-raff.yaml file; missing ${missingDefinitions.length} definitions`;\n\n console.log(`${message} (details below)`);\n console.table(checks);\n\n throw new Error(message);\n }\n }\n\n private validateAllRegionsAreResolved(): void {\n const unresolved = this.allRegions.filter((region) => Token.isUnresolved(region));\n\n if (unresolved.length !== 0) {\n throw new Error(`Unable to produce a working riff-raff.yaml file; all stacks must have an explicit region set`);\n }\n }\n\n private getLambdas(cdkStack: GuStack): GuLambdaFunction[] {\n return cdkStack.node.findAll().filter((_) => _ instanceof GuLambdaFunction) as GuLambdaFunction[];\n }\n\n private getAutoScalingGroups(cdkStack: GuStack): GuAutoScalingGroup[] {\n return cdkStack.node.findAll().filter((_) => _ instanceof GuAutoScalingGroup) as GuAutoScalingGroup[];\n }\n\n private getGuStackDependencies(cdkStack: GuStack): GuStack[] {\n return cdkStack.dependencies.filter((_) => _ instanceof GuStack) as GuStack[];\n }\n\n // eslint-disable-next-line custom-rules/valid-constructors -- this needs to sit above GuStack on the cdk tree\n constructor(app: App) {\n this.allCdkStacks = app.node.findAll().filter((_) => _ instanceof GuStack) as GuStack[];\n const allowedStages = new Set(this.allCdkStacks.map(({ stage }) => stage));\n this.allStageTags = Array.from(allowedStages);\n this.allStackTags = Array.from(new Set(this.allCdkStacks.map(({ stack }) => stack)));\n this.allRegions = Array.from(new Set(this.allCdkStacks.map(({ region }) => region)));\n\n this.validateStacksInApp();\n this.validateAllRegionsAreResolved();\n\n this.outdir = app.outdir;\n\n const deployments = new Map<RiffRaffDeploymentName, RiffRaffDeploymentProps>();\n\n const groupedStacks: GroupedCdkStacks = groupByClassNameStackRegionStage(this.allCdkStacks);\n\n Object.values(groupedStacks).forEach((stackTagGroup) => {\n Object.values(stackTagGroup).forEach((regionGroup) => {\n Object.values(regionGroup).forEach((stageGroup) => {\n const stacks: GuStack[] = Object.values(stageGroup).flat();\n\n // The items in `stacks` only differ by stage, so we can just use the first item in the list.\n const [stack] = stacks;\n\n if (!stack) {\n throw new Error(\"Unable to produce a working riff-raff.yaml file; there are no stacks!\");\n }\n\n const lambdas = this.getLambdas(stack);\n const autoscalingGroups = this.getAutoScalingGroups(stack);\n\n const artifactUploads: RiffRaffDeployment[] = [\n lambdas.filter((_) => !_.withoutArtifactUpload).map(uploadLambdaArtifact),\n autoscalingGroups.map(uploadAutoscalingArtifact),\n ].flat();\n artifactUploads.forEach(({ name, props }) => deployments.set(name, props));\n\n const parentStacks: RiffRaffDeployment[] = this.getGuStackDependencies(stack).map((x) =>\n cloudFormationDeployment([x], [], this.outdir),\n );\n\n const cfnDeployment = cloudFormationDeployment(stacks, [...artifactUploads, ...parentStacks], this.outdir);\n deployments.set(cfnDeployment.name, cfnDeployment.props);\n\n const lambdasWithoutAnAlias = lambdas.filter((lambda) => lambda.alias === undefined);\n // If the Lambda has an alias it is using versioning. When using versioning, there is no need for Riff-Raff\n // to modify the unpublished version of the function\n lambdasWithoutAnAlias.forEach((lambda) => {\n const lambdaDeployment = updateLambdaDeployment(lambda, cfnDeployment);\n deployments.set(lambdaDeployment.name, lambdaDeployment.props);\n });\n\n /*\n Instances in an ASG with an `AutoScalingRollingUpdate` update policy are rotated via CloudFormation.\n Therefore, they do not need to also perform an `autoscaling` deployment via Riff-Raff.\n */\n const legacyAutoscalingGroups = autoscalingGroups.filter((asg) => {\n const { cfnOptions } = asg.node.defaultChild as CfnAutoScalingGroup;\n const { updatePolicy } = cfnOptions;\n return updatePolicy?.autoScalingRollingUpdate === undefined;\n });\n\n legacyAutoscalingGroups.forEach((asg) => {\n const asgDeployment = autoscalingDeployment(asg, cfnDeployment);\n deployments.set(asgDeployment.name, asgDeployment.props);\n });\n\n const amiParametersToTags = getAmiParameters(autoscalingGroups);\n\n const minInServiceParamMap =\n GuHorizontallyScalingDeploymentPropertiesExperimental.getInstance(stack).asgToParamMap;\n const minInServiceAsgs = autoscalingGroups.filter((asg) => minInServiceParamMap.has(asg.node.id));\n const minInstancesInServiceParameters = getMinInstancesInServiceParameters(minInServiceAsgs);\n\n deployments.set(cfnDeployment.name, {\n ...cfnDeployment.props,\n parameters: {\n ...cfnDeployment.props.parameters,\n\n // only add the `amiParametersToTags` property if there are some\n ...(autoscalingGroups.length > 0 && { amiParametersToTags }),\n\n // only add the `minInstancesInServiceParameters` property if there are some\n ...(minInServiceAsgs.length > 0 && { minInstancesInServiceParameters }),\n },\n });\n });\n });\n });\n\n this.riffRaffYaml = {\n allowedStages,\n deployments,\n };\n }\n\n /**\n * The `riff-raff.yaml` file as a string.\n * Useful for testing.\n */\n toYAML(): string {\n // Add support for ES6 Set and Map. See https://github.com/nodeca/js-yaml/issues/436.\n const replacer = (_key: string, value: unknown) => {\n if (value instanceof Set) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- this is how `js-yaml` is typed\n return Array.from(value);\n }\n if (value instanceof Map) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- this is how `js-yaml` is typed\n return Object.fromEntries(value);\n }\n return value;\n };\n\n return dump(this.riffRaffYaml, { replacer });\n }\n\n /**\n * Write the `riff-raff.yaml` file to disk.\n * It'll be located with the CFN JSON templates generated by `cdk synth`.\n */\n synth(): void {\n const outPath = path.join(this.outdir, \"riff-raff.yaml\");\n writeFileSync(outPath, this.toYAML());\n }\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@guardian/cdk",
3
3
  "description": "Generic Guardian flavoured AWS CDK components",
4
- "version": "61.5.2",
4
+ "version": "61.7.0",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "files": [