@guardian/cdk 62.6.0 → 63.0.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.
- package/lib/constructs/core/stack.d.ts +9 -0
- package/lib/constructs/core/stack.d.ts.map +1 -1
- package/lib/constructs/core/stack.js +5 -1
- package/lib/constructs/core/stack.js.map +1 -1
- package/lib/constructs/root.d.ts.map +1 -1
- package/lib/constructs/root.js +9 -1
- package/lib/constructs/root.js.map +1 -1
- package/lib/experimental/patterns/ec2-app.d.ts +13 -13
- package/lib/experimental/patterns/ec2-app.d.ts.map +1 -1
- package/lib/experimental/patterns/ec2-app.js +42 -38
- package/lib/experimental/patterns/ec2-app.js.map +1 -1
- package/lib/riff-raff-yaml-file/deployments/cloudformation.d.ts +2 -1
- package/lib/riff-raff-yaml-file/deployments/cloudformation.d.ts.map +1 -1
- package/lib/riff-raff-yaml-file/deployments/cloudformation.js +7 -12
- package/lib/riff-raff-yaml-file/deployments/cloudformation.js.map +1 -1
- package/lib/riff-raff-yaml-file/deployments/update-parameters.d.ts +3 -0
- package/lib/riff-raff-yaml-file/deployments/update-parameters.d.ts.map +1 -0
- package/lib/riff-raff-yaml-file/deployments/update-parameters.js +18 -0
- package/lib/riff-raff-yaml-file/deployments/update-parameters.js.map +1 -0
- package/lib/riff-raff-yaml-file/group-by.d.ts +6 -2
- package/lib/riff-raff-yaml-file/group-by.d.ts.map +1 -1
- package/lib/riff-raff-yaml-file/group-by.js +9 -13
- package/lib/riff-raff-yaml-file/group-by.js.map +1 -1
- package/lib/riff-raff-yaml-file/index.d.ts +9 -74
- package/lib/riff-raff-yaml-file/index.d.ts.map +1 -1
- package/lib/riff-raff-yaml-file/index.js +145 -172
- package/lib/riff-raff-yaml-file/index.js.map +1 -1
- package/lib/riff-raff-yaml-file/types.d.ts +8 -1
- package/lib/riff-raff-yaml-file/types.d.ts.map +1 -1
- package/lib/riff-raff-yaml-file/types.js +7 -0
- package/lib/riff-raff-yaml-file/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -18,6 +18,14 @@ export interface GuStackProps extends Omit<StackProps, "stackName"> {
|
|
|
18
18
|
* Optional name of the app. If defined, all resources will have an App tag.
|
|
19
19
|
*/
|
|
20
20
|
app?: string;
|
|
21
|
+
/**
|
|
22
|
+
* The name of the Riff-Raff project used to deploy these resources.
|
|
23
|
+
* Used as the value of the `gu:riff-raff-project` tag on all resources.
|
|
24
|
+
* If using {@link GuRoot} or {@link RiffRaffYamlFile}, this value is also used in the location of the generated `riff-raff.yaml`.
|
|
25
|
+
*
|
|
26
|
+
* @see https://github.com/guardian/riffraff-platform
|
|
27
|
+
*/
|
|
28
|
+
riffRaffProjectName?: string;
|
|
21
29
|
/**
|
|
22
30
|
* The AWS CloudFormation stack name (as shown in the AWS CloudFormation UI).
|
|
23
31
|
* @defaultValue the `GU_CFN_STACK_NAME` environment variable
|
|
@@ -66,6 +74,7 @@ export declare class GuStack extends Stack implements StackStageIdentity {
|
|
|
66
74
|
readonly stack: string;
|
|
67
75
|
readonly stage: string;
|
|
68
76
|
readonly app: string | undefined;
|
|
77
|
+
readonly riffRaffProjectName: string | undefined;
|
|
69
78
|
/**
|
|
70
79
|
* The repository name, if it can be determined from the context or the git remote origin url.
|
|
71
80
|
* If it cannot be determined from either of these sources, it will be `undefined`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../../../src/constructs/core/stack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAc,UAAU,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAwB,YAAY,EAA0B,KAAK,EAAQ,MAAM,aAAa,CAAC;AACtG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAO7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC;IACjE;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,OAAQ,SAAQ,KAAM,YAAW,kBAAkB;IAC9D,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,SAAgB,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../../../src/constructs/core/stack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAc,UAAU,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAwB,YAAY,EAA0B,KAAK,EAAQ,MAAM,aAAa,CAAC;AACtG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAO7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC;IACjE;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,OAAQ,SAAQ,KAAM,YAAW,kBAAkB;IAC9D,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,SAAgB,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,SAAgB,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAC;IAExD;;;OAGG;IACH,SAAgB,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IAEnD;;;;;;;;;OASG;IACH,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,wBAAwB,GAAE,OAAc,GAAG,IAAI;IAM5F;;;;OAIG;IACH,IAAI,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAK7C;gBAGW,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY;IAmDvD;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAa3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqDG;IACI,iBAAiB,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,iBAAiB,GAAG,IAAI;CAQhG"}
|
|
@@ -67,7 +67,7 @@ class GuStack extends aws_cdk_lib_1.Stack {
|
|
|
67
67
|
}
|
|
68
68
|
// eslint-disable-next-line custom-rules/valid-constructors -- GuStack is the exception as it must take an App
|
|
69
69
|
constructor(scope, id, props) {
|
|
70
|
-
const { cloudFormationStackName = process.env.GU_CFN_STACK_NAME, stack, stage, app, withoutTags } = props;
|
|
70
|
+
const { cloudFormationStackName = process.env.GU_CFN_STACK_NAME, stack, stage, app, riffRaffProjectName, withoutTags, } = props;
|
|
71
71
|
super(scope, id, {
|
|
72
72
|
...props,
|
|
73
73
|
stackName: cloudFormationStackName,
|
|
@@ -78,6 +78,7 @@ class GuStack extends aws_cdk_lib_1.Stack {
|
|
|
78
78
|
this.stack = stack;
|
|
79
79
|
this.stage = stage.toUpperCase();
|
|
80
80
|
this.app = app;
|
|
81
|
+
this.riffRaffProjectName = riffRaffProjectName;
|
|
81
82
|
this.repositoryName = this.tryGetRepositoryTag();
|
|
82
83
|
if (!withoutTags) {
|
|
83
84
|
this.addTag(constants_1.TrackingTag.Key, constants_1.TrackingTag.Value);
|
|
@@ -86,6 +87,9 @@ class GuStack extends aws_cdk_lib_1.Stack {
|
|
|
86
87
|
if (this.app) {
|
|
87
88
|
this.addTag("App", this.app);
|
|
88
89
|
}
|
|
90
|
+
if (this.riffRaffProjectName) {
|
|
91
|
+
this.addTag("gu:riff-raff-project", this.riffRaffProjectName);
|
|
92
|
+
}
|
|
89
93
|
if (this.repositoryName) {
|
|
90
94
|
this.addTag(constants_1.MetadataKeys.REPOSITORY_NAME, this.repositoryName);
|
|
91
95
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack.js","sourceRoot":"","sources":["../../../src/constructs/core/stack.ts"],"names":[],"mappings":"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","sourcesContent":["import type { App, CfnElement, StackProps } from \"aws-cdk-lib\";\nimport { Annotations, Aspects, CfnParameter, LegacyStackSynthesizer, Stack, Tags } from \"aws-cdk-lib\";\nimport type { IConstruct } from \"constructs\";\nimport gitUrlParse from \"git-url-parse\";\nimport { CfnIncludeReporter } from \"../../aspects/cfn-include-reporter\";\nimport { CfnParameterReporter } from \"../../aspects/cfn-parameter-reporter\";\nimport { Metadata } from \"../../aspects/metadata\";\nimport { ContextKeys, MetadataKeys, TrackingTag } from \"../../constants\";\nimport { gitRemoteOriginUrl } from \"../../utils/git\";\nimport type { StackStageIdentity } from \"./identity\";\nimport type { GuStaticLogicalId } from \"./migrating\";\n\nexport interface GuStackProps extends Omit<StackProps, \"stackName\"> {\n /**\n * The Guardian stack being used (as defined in your riff-raff.yaml).\n * This will be applied as a tag to all of your resources.\n */\n stack: string;\n\n /**\n * The stage being used (as defined in your riff-raff.yaml).\n * This will be applied as a tag to all of your resources.\n */\n stage: string;\n\n /**\n * Optional name of the app. If defined, all resources will have an App tag.\n */\n app?: string;\n\n /**\n * The AWS CloudFormation stack name (as shown in the AWS CloudFormation UI).\n * @defaultValue the `GU_CFN_STACK_NAME` environment variable\n */\n cloudFormationStackName?: string;\n /**\n * Set this to true to stop the GuStack from tagging all of your AWS resources.\n * This should only be turned on as part of an initial migration from CloudFormation.\n */\n withoutTags?: boolean;\n\n /**\n * Set to disable CDK metadata. Only for internal use (for disabling for some\n * snapshot tests). We rely on tracking data to prioritise future work so\n * please do not override this.\n */\n withoutMetadata?: boolean;\n}\n\n/**\n * GuStack provides the `stack` and `stage` parameters to a template.\n * It also takes the `app` in the constructor.\n *\n * GuStack will add the Stack, Stage and App tags to all resources.\n *\n * GuStack also adds the tag `X-Gu-CDK-Version`.\n * This tag allows us to measure adoption of this library.\n * It's value is the version of guardian/cdk being used, as defined in `package.json`.\n * As a result, the change sets between version numbers will be fairly noisy,\n * as all resources receive a tag update.\n * It is recommended to upgrade the version of @guardian/cdk being used in two steps:\n * 1. Bump the library, apply the tag updates\n * 2. Make any other stack changes\n *\n * Typical usage is to extend GuStack:\n *\n * ```typescript\n * class MyStack extends GuStack {\n * constructor(scope: App, id: string, props: GuStackProps) {\n * super(scope, id, props)\n * }\n *\n * // add resources here\n * }\n * ```\n */\nexport class GuStack extends Stack implements StackStageIdentity {\n public readonly stack: string;\n public readonly stage: string;\n public readonly app: string | undefined;\n\n /**\n * The repository name, if it can be determined from the context or the git remote origin url.\n * If it cannot be determined from either of these sources, it will be `undefined`.\n */\n public readonly repositoryName: string | undefined;\n\n /**\n * A helper function to add a tag to all resources in a stack.\n *\n * Note: tags will be listed in alphabetical order during synthesis.\n *\n * @param key the tag name\n * @param value the value of the tag\n * @param applyToLaunchedInstances whether or not to apply the tag to instances launched in an ASG.\n * @protected\n */\n protected addTag(key: string, value: string, applyToLaunchedInstances: boolean = true): void {\n // TODO add validation for `key` and `value`\n // see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html\n Tags.of(this).add(key, value, { applyToLaunchedInstances });\n }\n\n /**\n * Returns all the parameters on this stack.\n *\n * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html\n */\n get parameters(): Record<string, CfnParameter> {\n return this.node\n .findAll()\n .filter((construct) => construct instanceof CfnParameter)\n .reduce((acc, param) => ({ ...acc, [param.node.id]: param as CfnParameter }), {});\n }\n\n // eslint-disable-next-line custom-rules/valid-constructors -- GuStack is the exception as it must take an App\n constructor(scope: App, id: string, props: GuStackProps) {\n const { cloudFormationStackName = process.env.GU_CFN_STACK_NAME, stack, stage, app, withoutTags } = props;\n\n super(scope, id, {\n ...props,\n stackName: cloudFormationStackName,\n\n // TODO Use `DefaultStackSynthesizer` or create own synthesizer?\n // see https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-custom-synth\n synthesizer: new LegacyStackSynthesizer(),\n });\n\n this.stack = stack;\n this.stage = stage.toUpperCase();\n this.app = app;\n this.repositoryName = this.tryGetRepositoryTag();\n\n if (!withoutTags) {\n this.addTag(TrackingTag.Key, TrackingTag.Value);\n\n this.addTag(\"Stack\", this.stack);\n this.addTag(\"Stage\", this.stage);\n if (this.app) {\n this.addTag(\"App\", this.app);\n }\n\n if (this.repositoryName) {\n this.addTag(MetadataKeys.REPOSITORY_NAME, this.repositoryName);\n }\n }\n\n if (!props.withoutMetadata) {\n Aspects.of(this).add(new Metadata(this));\n }\n\n Aspects.of(this).add(new CfnIncludeReporter());\n Aspects.of(this).add(new CfnParameterReporter());\n }\n\n /**\n * Returns the repository name.\n * The value is retrieved in the following order:\n * 1. From the context\n * 2. From git config\n *\n * @private\n */\n private tryGetRepositoryTag(): string | undefined {\n try {\n const urlFromContext = this.node.tryGetContext(ContextKeys.REPOSITORY_URL) as string | undefined;\n const repositoryUrl: string = urlFromContext ?? gitRemoteOriginUrl();\n return gitUrlParse(repositoryUrl).full_name;\n } catch {\n console.info(\n `Unable to find git repository name. Set the ${ContextKeys.REPOSITORY_URL} context value or configure a git remote`,\n );\n return undefined;\n }\n }\n\n /**\n * Override the auto-generated logical ID for a resource, in the generated CloudFormation template, with a static one.\n *\n * Of particular use when migrating a JSON/YAML CloudFormation template into GuCDK.\n * It's generally advised to retain the logical ID for stateful resources, such as databases or buckets.\n *\n * Let's say we have a YAML template:\n *\n * ```yaml\n * AWSTemplateFormatVersion: '2010-09-09'\n * Resources:\n * UsesTable:\n * Type: AWS::DynamoDB::Table\n * Properties:\n * TableName: !Sub 'users-${stage}'\n * ```\n *\n * When moving to GuCDK we'll have this:\n *\n * class MyStack extends GuStack {\n * constructor(app: App, id: string, props: GuStackProps) {\n * super(app, id, props);\n *\n * const { stage } = this;\n *\n * new Table(this, \"UsersTable\", {\n * name: `users-${stage}`\n * });\n * }\n * }\n *\n * During synthesis, CDK auto-generates logical IDs, so we'll have a stack with a DynamoDB table named 'UsersTable<SOME GUID>', NOT `UsersTable`.\n * That is, the `UsersTable` table will be deleted.\n *\n * In order to retain the original ID from the YAML template, we will do:\n *\n * class MyStack extends GuStack {\n * constructor(app: App, id: string, props: GuStackProps) {\n * super(app, id, props);\n *\n * const { stage } = this;\n *\n * const table = new Table(this, \"UsersTable\", {\n * name: `users-${stage}`\n * });\n *\n * this.overrideLogicalId(table, { logicalId: \"UsersTable\", reason: \"Retaining a stateful resource from the YAML template\" });\n * }\n * }\n *\n * @param construct The (stateful) resource to retain the logical ID of.\n * @param logicalId The logical ID of the resource (as defined in the JSON/YAML template.\n * @param reason A small explanation to keep the logical ID. Mainly used to help future developers.\n */\n public overrideLogicalId(construct: IConstruct, { logicalId, reason }: GuStaticLogicalId): void {\n const {\n node: { id, defaultChild },\n } = construct;\n\n (defaultChild as CfnElement).overrideLogicalId(logicalId);\n Annotations.of(construct).addInfo(`Setting logical ID for ${id} to ${logicalId}. Reason: ${reason}`);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"stack.js","sourceRoot":"","sources":["../../../src/constructs/core/stack.ts"],"names":[],"mappings":"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","sourcesContent":["import type { App, CfnElement, StackProps } from \"aws-cdk-lib\";\nimport { Annotations, Aspects, CfnParameter, LegacyStackSynthesizer, Stack, Tags } from \"aws-cdk-lib\";\nimport type { IConstruct } from \"constructs\";\nimport gitUrlParse from \"git-url-parse\";\nimport { CfnIncludeReporter } from \"../../aspects/cfn-include-reporter\";\nimport { CfnParameterReporter } from \"../../aspects/cfn-parameter-reporter\";\nimport { Metadata } from \"../../aspects/metadata\";\nimport { ContextKeys, MetadataKeys, TrackingTag } from \"../../constants\";\nimport { gitRemoteOriginUrl } from \"../../utils/git\";\nimport type { StackStageIdentity } from \"./identity\";\nimport type { GuStaticLogicalId } from \"./migrating\";\n\nexport interface GuStackProps extends Omit<StackProps, \"stackName\"> {\n /**\n * The Guardian stack being used (as defined in your riff-raff.yaml).\n * This will be applied as a tag to all of your resources.\n */\n stack: string;\n\n /**\n * The stage being used (as defined in your riff-raff.yaml).\n * This will be applied as a tag to all of your resources.\n */\n stage: string;\n\n /**\n * Optional name of the app. If defined, all resources will have an App tag.\n */\n app?: string;\n\n /**\n * The name of the Riff-Raff project used to deploy these resources.\n * Used as the value of the `gu:riff-raff-project` tag on all resources.\n * If using {@link GuRoot} or {@link RiffRaffYamlFile}, this value is also used in the location of the generated `riff-raff.yaml`.\n *\n * @see https://github.com/guardian/riffraff-platform\n */\n riffRaffProjectName?: string;\n\n /**\n * The AWS CloudFormation stack name (as shown in the AWS CloudFormation UI).\n * @defaultValue the `GU_CFN_STACK_NAME` environment variable\n */\n cloudFormationStackName?: string;\n /**\n * Set this to true to stop the GuStack from tagging all of your AWS resources.\n * This should only be turned on as part of an initial migration from CloudFormation.\n */\n withoutTags?: boolean;\n\n /**\n * Set to disable CDK metadata. Only for internal use (for disabling for some\n * snapshot tests). We rely on tracking data to prioritise future work so\n * please do not override this.\n */\n withoutMetadata?: boolean;\n}\n\n/**\n * GuStack provides the `stack` and `stage` parameters to a template.\n * It also takes the `app` in the constructor.\n *\n * GuStack will add the Stack, Stage and App tags to all resources.\n *\n * GuStack also adds the tag `X-Gu-CDK-Version`.\n * This tag allows us to measure adoption of this library.\n * It's value is the version of guardian/cdk being used, as defined in `package.json`.\n * As a result, the change sets between version numbers will be fairly noisy,\n * as all resources receive a tag update.\n * It is recommended to upgrade the version of @guardian/cdk being used in two steps:\n * 1. Bump the library, apply the tag updates\n * 2. Make any other stack changes\n *\n * Typical usage is to extend GuStack:\n *\n * ```typescript\n * class MyStack extends GuStack {\n * constructor(scope: App, id: string, props: GuStackProps) {\n * super(scope, id, props)\n * }\n *\n * // add resources here\n * }\n * ```\n */\nexport class GuStack extends Stack implements StackStageIdentity {\n public readonly stack: string;\n public readonly stage: string;\n public readonly app: string | undefined;\n public readonly riffRaffProjectName: string | undefined;\n\n /**\n * The repository name, if it can be determined from the context or the git remote origin url.\n * If it cannot be determined from either of these sources, it will be `undefined`.\n */\n public readonly repositoryName: string | undefined;\n\n /**\n * A helper function to add a tag to all resources in a stack.\n *\n * Note: tags will be listed in alphabetical order during synthesis.\n *\n * @param key the tag name\n * @param value the value of the tag\n * @param applyToLaunchedInstances whether or not to apply the tag to instances launched in an ASG.\n * @protected\n */\n protected addTag(key: string, value: string, applyToLaunchedInstances: boolean = true): void {\n // TODO add validation for `key` and `value`\n // see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html\n Tags.of(this).add(key, value, { applyToLaunchedInstances });\n }\n\n /**\n * Returns all the parameters on this stack.\n *\n * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html\n */\n get parameters(): Record<string, CfnParameter> {\n return this.node\n .findAll()\n .filter((construct) => construct instanceof CfnParameter)\n .reduce((acc, param) => ({ ...acc, [param.node.id]: param as CfnParameter }), {});\n }\n\n // eslint-disable-next-line custom-rules/valid-constructors -- GuStack is the exception as it must take an App\n constructor(scope: App, id: string, props: GuStackProps) {\n const {\n cloudFormationStackName = process.env.GU_CFN_STACK_NAME,\n stack,\n stage,\n app,\n riffRaffProjectName,\n withoutTags,\n } = props;\n\n super(scope, id, {\n ...props,\n stackName: cloudFormationStackName,\n\n // TODO Use `DefaultStackSynthesizer` or create own synthesizer?\n // see https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-custom-synth\n synthesizer: new LegacyStackSynthesizer(),\n });\n\n this.stack = stack;\n this.stage = stage.toUpperCase();\n this.app = app;\n this.riffRaffProjectName = riffRaffProjectName;\n this.repositoryName = this.tryGetRepositoryTag();\n\n if (!withoutTags) {\n this.addTag(TrackingTag.Key, TrackingTag.Value);\n\n this.addTag(\"Stack\", this.stack);\n this.addTag(\"Stage\", this.stage);\n if (this.app) {\n this.addTag(\"App\", this.app);\n }\n\n if (this.riffRaffProjectName) {\n this.addTag(\"gu:riff-raff-project\", this.riffRaffProjectName);\n }\n\n if (this.repositoryName) {\n this.addTag(MetadataKeys.REPOSITORY_NAME, this.repositoryName);\n }\n }\n\n if (!props.withoutMetadata) {\n Aspects.of(this).add(new Metadata(this));\n }\n\n Aspects.of(this).add(new CfnIncludeReporter());\n Aspects.of(this).add(new CfnParameterReporter());\n }\n\n /**\n * Returns the repository name.\n * The value is retrieved in the following order:\n * 1. From the context\n * 2. From git config\n *\n * @private\n */\n private tryGetRepositoryTag(): string | undefined {\n try {\n const urlFromContext = this.node.tryGetContext(ContextKeys.REPOSITORY_URL) as string | undefined;\n const repositoryUrl: string = urlFromContext ?? gitRemoteOriginUrl();\n return gitUrlParse(repositoryUrl).full_name;\n } catch {\n console.info(\n `Unable to find git repository name. Set the ${ContextKeys.REPOSITORY_URL} context value or configure a git remote`,\n );\n return undefined;\n }\n }\n\n /**\n * Override the auto-generated logical ID for a resource, in the generated CloudFormation template, with a static one.\n *\n * Of particular use when migrating a JSON/YAML CloudFormation template into GuCDK.\n * It's generally advised to retain the logical ID for stateful resources, such as databases or buckets.\n *\n * Let's say we have a YAML template:\n *\n * ```yaml\n * AWSTemplateFormatVersion: '2010-09-09'\n * Resources:\n * UsesTable:\n * Type: AWS::DynamoDB::Table\n * Properties:\n * TableName: !Sub 'users-${stage}'\n * ```\n *\n * When moving to GuCDK we'll have this:\n *\n * class MyStack extends GuStack {\n * constructor(app: App, id: string, props: GuStackProps) {\n * super(app, id, props);\n *\n * const { stage } = this;\n *\n * new Table(this, \"UsersTable\", {\n * name: `users-${stage}`\n * });\n * }\n * }\n *\n * During synthesis, CDK auto-generates logical IDs, so we'll have a stack with a DynamoDB table named 'UsersTable<SOME GUID>', NOT `UsersTable`.\n * That is, the `UsersTable` table will be deleted.\n *\n * In order to retain the original ID from the YAML template, we will do:\n *\n * class MyStack extends GuStack {\n * constructor(app: App, id: string, props: GuStackProps) {\n * super(app, id, props);\n *\n * const { stage } = this;\n *\n * const table = new Table(this, \"UsersTable\", {\n * name: `users-${stage}`\n * });\n *\n * this.overrideLogicalId(table, { logicalId: \"UsersTable\", reason: \"Retaining a stateful resource from the YAML template\" });\n * }\n * }\n *\n * @param construct The (stateful) resource to retain the logical ID of.\n * @param logicalId The logical ID of the resource (as defined in the JSON/YAML template.\n * @param reason A small explanation to keep the logical ID. Mainly used to help future developers.\n */\n public overrideLogicalId(construct: IConstruct, { logicalId, reason }: GuStaticLogicalId): void {\n const {\n node: { id, defaultChild },\n } = construct;\n\n (defaultChild as CfnElement).overrideLogicalId(logicalId);\n Annotations.of(construct).addInfo(`Setting logical ID for ${id} to ${logicalId}. Reason: ${reason}`);\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../src/constructs/root.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,MAAO,SAAQ,GAAG;IACpB,KAAK,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,aAAa;
|
|
1
|
+
{"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../src/constructs/root.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,MAAO,SAAQ,GAAG;IACpB,KAAK,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,aAAa;CAc/D"}
|
package/lib/constructs/root.js
CHANGED
|
@@ -30,8 +30,16 @@ const riff_raff_yaml_file_1 = require("../riff-raff-yaml-file");
|
|
|
30
30
|
*/
|
|
31
31
|
class GuRoot extends aws_cdk_lib_1.App {
|
|
32
32
|
synth(options) {
|
|
33
|
+
const cloudAssembly = super.synth(options);
|
|
34
|
+
/**
|
|
35
|
+
* {@link RiffRaffYamlFile} requires all Aspects to have been evaluated, specifically {@link GuHorizontallyScalingDeploymentPropertiesExperimental}.
|
|
36
|
+
* Aspects are evaluated during creation of a {@link CloudAssembly}.
|
|
37
|
+
* Therefore {@link RiffRaffYamlFile} must come after the call to `super.synth`.
|
|
38
|
+
*
|
|
39
|
+
* @see https://github.com/aws/aws-cdk/issues/29047
|
|
40
|
+
*/
|
|
33
41
|
new riff_raff_yaml_file_1.RiffRaffYamlFile(this).synth();
|
|
34
|
-
return
|
|
42
|
+
return cloudAssembly;
|
|
35
43
|
}
|
|
36
44
|
}
|
|
37
45
|
exports.GuRoot = GuRoot;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root.js","sourceRoot":"","sources":["../../src/constructs/root.ts"],"names":[],"mappings":";;;AACA,6CAAkC;AAElC,gEAA0D;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAa,MAAO,SAAQ,iBAAG;IACpB,KAAK,CAAC,OAA+B;QAC5C,
|
|
1
|
+
{"version":3,"file":"root.js","sourceRoot":"","sources":["../../src/constructs/root.ts"],"names":[],"mappings":";;;AACA,6CAAkC;AAElC,gEAA0D;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAa,MAAO,SAAQ,iBAAG;IACpB,KAAK,CAAC,OAA+B;QAC5C,MAAM,aAAa,GAAkB,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAE1D;;;;;;WAMG;QACH,IAAI,sCAAgB,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;QAEnC,OAAO,aAAa,CAAC;IACvB,CAAC;CACF;AAfD,wBAeC","sourcesContent":["import type { StageSynthesisOptions } from \"aws-cdk-lib\";\nimport { App } from \"aws-cdk-lib\";\nimport type { CloudAssembly } from \"aws-cdk-lib/cx-api\";\nimport { RiffRaffYamlFile } from \"../riff-raff-yaml-file\";\n\n/**\n * A replacement for `App`, sitting at the root of a CDK application.\n * `GuRoot` will synthesise a `riff-raff.yaml` file for a CDK application.\n *\n * Usage is a case of updating `/<repo-root>/cdk/bin/cdk.ts` from:\n *\n * ```ts\n * import { App } from \"aws-cdk-lib\";\n *\n * const app = new App();\n *\n * new MyStack(app, \"my-stack-CODE\", {});\n * new MyStack(app, \"my-stack-PROD\", {});\n * ```\n * To:\n *\n * ```ts\n * import { GuRoot } from \"@guardian/cdk/lib/constructs/root\";\n *\n * const app = new GuRoot();\n *\n * new MyStack(app, \"my-stack-CODE\", {});\n * new MyStack(app, \"my-stack-PROD\", {});\n * ```\n */\nexport class GuRoot extends App {\n override synth(options?: StageSynthesisOptions): CloudAssembly {\n const cloudAssembly: CloudAssembly = super.synth(options);\n\n /**\n * {@link RiffRaffYamlFile} requires all Aspects to have been evaluated, specifically {@link GuHorizontallyScalingDeploymentPropertiesExperimental}.\n * Aspects are evaluated during creation of a {@link CloudAssembly}.\n * Therefore {@link RiffRaffYamlFile} must come after the call to `super.synth`.\n *\n * @see https://github.com/aws/aws-cdk/issues/29047\n */\n new RiffRaffYamlFile(this).synth();\n\n return cloudAssembly;\n }\n}\n"]}
|
|
@@ -3,7 +3,7 @@ import { CfnParameter, Duration } from "aws-cdk-lib";
|
|
|
3
3
|
import { UpdatePolicy } from "aws-cdk-lib/aws-autoscaling";
|
|
4
4
|
import type { IConstruct } from "constructs";
|
|
5
5
|
import { GuAutoScalingGroup } from "../../constructs/autoscaling";
|
|
6
|
-
import
|
|
6
|
+
import { GuStack } from "../../constructs/core";
|
|
7
7
|
import type { GuApplicationTargetGroup } from "../../constructs/loadbalancing";
|
|
8
8
|
import type { GuEc2AppProps } from "../../patterns";
|
|
9
9
|
import { GuEc2App } from "../../patterns";
|
|
@@ -22,6 +22,9 @@ export declare const RollingUpdateDurations: AutoScalingRollingUpdateDurations;
|
|
|
22
22
|
* Ensures the `AutoScalingRollingUpdate` of an AutoScaling Group has a `PauseTime` matching the healthcheck grace period.
|
|
23
23
|
* It also ensures the `CreationPolicy` resource signal `Timeout` matches the healthcheck grace period.
|
|
24
24
|
*
|
|
25
|
+
* @note
|
|
26
|
+
* Be sure to instantiate this only once for an entire {@link GuStack}, else the properties of the ASG will be incorrectly updated multiple times.
|
|
27
|
+
*
|
|
25
28
|
* @internal
|
|
26
29
|
*
|
|
27
30
|
* @privateRemarks
|
|
@@ -32,10 +35,6 @@ export declare const RollingUpdateDurations: AutoScalingRollingUpdateDurations;
|
|
|
32
35
|
* TODO Expose the healthcheck grace period as a property on {@link GuEc2App} and remove this `Aspect`.
|
|
33
36
|
*/
|
|
34
37
|
export declare class GuAutoScalingRollingUpdateTimeoutExperimental implements IAspect {
|
|
35
|
-
readonly stack: GuStack;
|
|
36
|
-
private static instance;
|
|
37
|
-
private constructor();
|
|
38
|
-
static getInstance(stack: GuStack): GuAutoScalingRollingUpdateTimeoutExperimental;
|
|
39
38
|
visit(construct: IConstruct): void;
|
|
40
39
|
}
|
|
41
40
|
/**
|
|
@@ -52,21 +51,22 @@ export declare class GuAutoScalingRollingUpdateTimeoutExperimental implements IA
|
|
|
52
51
|
* - If the service is fully scaled, it'll be set to (at least) `MaxSize` - 1
|
|
53
52
|
*
|
|
54
53
|
* @privateRemarks
|
|
55
|
-
* - Temporarily implemented as a singleton to ensure only one instance is added to a stack,
|
|
56
|
-
* else multiple attempts to add the same CFN Parameter will be made and fail.
|
|
57
54
|
* - Once out of experimental, instantiate this `Aspect` directly in {@link GuStack}.
|
|
58
55
|
*
|
|
59
56
|
* @see https://github.com/guardian/testing-asg-rolling-update
|
|
60
57
|
*/
|
|
61
58
|
export declare class GuHorizontallyScalingDeploymentPropertiesExperimental implements IAspect {
|
|
62
|
-
readonly stack: GuStack;
|
|
63
|
-
readonly asgToParamMap: Map<string, CfnParameter>;
|
|
64
|
-
private static instance;
|
|
65
|
-
private constructor();
|
|
66
|
-
static getInstance(stack: GuStack): GuHorizontallyScalingDeploymentPropertiesExperimental;
|
|
67
59
|
visit(construct: IConstruct): void;
|
|
68
60
|
}
|
|
69
|
-
|
|
61
|
+
/**
|
|
62
|
+
* A CloudFormation parameter used to make the `MinInstancesInService` property of an ASG dynamic during deployment with Riff-Raff.
|
|
63
|
+
* This shouldn't be instantiated directly, but via {@link GuHorizontallyScalingDeploymentPropertiesExperimental}.
|
|
64
|
+
*/
|
|
65
|
+
export declare class GuAsgMinInstancesInServiceParameterExperimental extends CfnParameter {
|
|
66
|
+
readonly asg: GuAutoScalingGroup;
|
|
67
|
+
private constructor();
|
|
68
|
+
static getInstance(autoScalingGroup: GuAutoScalingGroup): GuAsgMinInstancesInServiceParameterExperimental;
|
|
69
|
+
}
|
|
70
70
|
export interface GuEc2AppExperimentalProps extends Omit<GuEc2AppProps, "updatePolicy"> {
|
|
71
71
|
/**
|
|
72
72
|
* Which application build to run.
|
|
@@ -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;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,
|
|
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,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,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;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,6CAA8C,YAAW,OAAO;IACpE,KAAK,CAAC,SAAS,EAAE,UAAU;CAiCnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,qDAAsD,YAAW,OAAO;IAC5E,KAAK,CAAC,SAAS,EAAE,UAAU;CAiDnC;AAED;;;GAGG;AACH,qBAAa,+CAAgD,SAAQ,YAAY;IAC/E,SAAgB,GAAG,EAAE,kBAAkB,CAAC;IAExC,OAAO;WAKO,WAAW,CAAC,gBAAgB,EAAE,kBAAkB,GAAG,+CAA+C;CAoBjH;AA8CD,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC;IACpF;;;;;;OAMG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,EAAE,QAAQ,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,qBAAa,iCAAiC;IAC5C,MAAM,CAAC,SAAS,CACd,gBAAgB,EAAE,MAAM,EACxB,gBAAgB,CAAC,EAAE,MAAM,EACzB,iBAAiB,GAAE,QAA8B;CAmCpD;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;IACxB,iBAAiB,CAAC,EAAE,QAAQ,CAAC;CAC9B;AAqBD;;;;;;;;GAQG;AACH,qBAAa,sCAAsC;gBACrC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,2CAA2C;CAoF/E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,oBAAqB,SAAQ,QAAQ;gBACpC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,yBAAyB;CAuD7D"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.GuEc2AppExperimental = exports.GuUserDataForRollingUpdateExperimental = exports.GuRoleForRollingUpdateExperimental = exports.GuRollingUpdatePolicyExperimental = exports.
|
|
3
|
+
exports.GuEc2AppExperimental = exports.GuUserDataForRollingUpdateExperimental = exports.GuRoleForRollingUpdateExperimental = exports.GuRollingUpdatePolicyExperimental = exports.GuAsgMinInstancesInServiceParameterExperimental = 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");
|
|
7
7
|
const constants_1 = require("../../constants");
|
|
8
8
|
const autoscaling_1 = require("../../constructs/autoscaling");
|
|
9
|
+
const core_1 = require("../../constructs/core");
|
|
9
10
|
const patterns_1 = require("../../patterns");
|
|
10
11
|
const singleton_1 = require("../../utils/singleton");
|
|
11
12
|
exports.RollingUpdateDurations = {
|
|
@@ -16,6 +17,9 @@ exports.RollingUpdateDurations = {
|
|
|
16
17
|
* Ensures the `AutoScalingRollingUpdate` of an AutoScaling Group has a `PauseTime` matching the healthcheck grace period.
|
|
17
18
|
* It also ensures the `CreationPolicy` resource signal `Timeout` matches the healthcheck grace period.
|
|
18
19
|
*
|
|
20
|
+
* @note
|
|
21
|
+
* Be sure to instantiate this only once for an entire {@link GuStack}, else the properties of the ASG will be incorrectly updated multiple times.
|
|
22
|
+
*
|
|
19
23
|
* @internal
|
|
20
24
|
*
|
|
21
25
|
* @privateRemarks
|
|
@@ -26,15 +30,6 @@ exports.RollingUpdateDurations = {
|
|
|
26
30
|
* TODO Expose the healthcheck grace period as a property on {@link GuEc2App} and remove this `Aspect`.
|
|
27
31
|
*/
|
|
28
32
|
class GuAutoScalingRollingUpdateTimeoutExperimental {
|
|
29
|
-
constructor(scope) {
|
|
30
|
-
this.stack = scope;
|
|
31
|
-
}
|
|
32
|
-
static getInstance(stack) {
|
|
33
|
-
if (!this.instance || !(0, singleton_1.isSingletonPresentInStack)(stack, this.instance)) {
|
|
34
|
-
this.instance = new GuAutoScalingRollingUpdateTimeoutExperimental(stack);
|
|
35
|
-
}
|
|
36
|
-
return this.instance;
|
|
37
|
-
}
|
|
38
33
|
visit(construct) {
|
|
39
34
|
if (construct instanceof aws_autoscaling_1.CfnAutoScalingGroup) {
|
|
40
35
|
const currentRollingUpdate = construct.cfnOptions.updatePolicy?.autoScalingRollingUpdate;
|
|
@@ -78,25 +73,13 @@ exports.GuAutoScalingRollingUpdateTimeoutExperimental = GuAutoScalingRollingUpda
|
|
|
78
73
|
* - If the service is fully scaled, it'll be set to (at least) `MaxSize` - 1
|
|
79
74
|
*
|
|
80
75
|
* @privateRemarks
|
|
81
|
-
* - Temporarily implemented as a singleton to ensure only one instance is added to a stack,
|
|
82
|
-
* else multiple attempts to add the same CFN Parameter will be made and fail.
|
|
83
76
|
* - Once out of experimental, instantiate this `Aspect` directly in {@link GuStack}.
|
|
84
77
|
*
|
|
85
78
|
* @see https://github.com/guardian/testing-asg-rolling-update
|
|
86
79
|
*/
|
|
87
80
|
class GuHorizontallyScalingDeploymentPropertiesExperimental {
|
|
88
|
-
constructor(scope) {
|
|
89
|
-
this.stack = scope;
|
|
90
|
-
this.asgToParamMap = new Map();
|
|
91
|
-
}
|
|
92
|
-
static getInstance(stack) {
|
|
93
|
-
if (!this.instance || !(0, singleton_1.isSingletonPresentInStack)(stack, this.instance)) {
|
|
94
|
-
this.instance = new GuHorizontallyScalingDeploymentPropertiesExperimental(stack);
|
|
95
|
-
}
|
|
96
|
-
return this.instance;
|
|
97
|
-
}
|
|
98
81
|
visit(construct) {
|
|
99
|
-
if (
|
|
82
|
+
if (aws_autoscaling_1.CfnScalingPolicy.isCfnScalingPolicy(construct)) {
|
|
100
83
|
const { node } = construct;
|
|
101
84
|
const { scopes, path } = node;
|
|
102
85
|
/*
|
|
@@ -126,14 +109,7 @@ class GuHorizontallyScalingDeploymentPropertiesExperimental {
|
|
|
126
109
|
undoing any changes that a scale-out event may have done.
|
|
127
110
|
*/
|
|
128
111
|
cfnAutoScalingGroup.desiredCapacity = undefined;
|
|
129
|
-
const
|
|
130
|
-
if (!this.asgToParamMap.has(asgNodeId)) {
|
|
131
|
-
const cfnParameterName = getAsgRollingUpdateCfnParameterName(autoScalingGroup);
|
|
132
|
-
this.asgToParamMap.set(asgNodeId, new aws_cdk_lib_1.CfnParameter(this.stack, cfnParameterName, {
|
|
133
|
-
type: "Number",
|
|
134
|
-
}));
|
|
135
|
-
}
|
|
136
|
-
const minInstancesInService = this.asgToParamMap.get(asgNodeId);
|
|
112
|
+
const minInstancesInService = GuAsgMinInstancesInServiceParameterExperimental.getInstance(autoScalingGroup);
|
|
137
113
|
cfnAutoScalingGroup.cfnOptions.updatePolicy = {
|
|
138
114
|
autoScalingRollingUpdate: {
|
|
139
115
|
...currentRollingUpdate,
|
|
@@ -145,11 +121,31 @@ class GuHorizontallyScalingDeploymentPropertiesExperimental {
|
|
|
145
121
|
}
|
|
146
122
|
}
|
|
147
123
|
exports.GuHorizontallyScalingDeploymentPropertiesExperimental = GuHorizontallyScalingDeploymentPropertiesExperimental;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
124
|
+
/**
|
|
125
|
+
* A CloudFormation parameter used to make the `MinInstancesInService` property of an ASG dynamic during deployment with Riff-Raff.
|
|
126
|
+
* This shouldn't be instantiated directly, but via {@link GuHorizontallyScalingDeploymentPropertiesExperimental}.
|
|
127
|
+
*/
|
|
128
|
+
class GuAsgMinInstancesInServiceParameterExperimental extends aws_cdk_lib_1.CfnParameter {
|
|
129
|
+
constructor(scope, id, autoScalingGroup) {
|
|
130
|
+
super(scope, id, { type: "Number" });
|
|
131
|
+
this.asg = autoScalingGroup;
|
|
132
|
+
}
|
|
133
|
+
static getInstance(autoScalingGroup) {
|
|
134
|
+
const stack = core_1.GuStack.of(autoScalingGroup);
|
|
135
|
+
const { app } = autoScalingGroup;
|
|
136
|
+
const id = `MinInstancesInServiceFor${app.replaceAll("-", "")}`;
|
|
137
|
+
const maybeParameter = stack.parameters[id];
|
|
138
|
+
if (!maybeParameter) {
|
|
139
|
+
return new GuAsgMinInstancesInServiceParameterExperimental(stack, id, autoScalingGroup);
|
|
140
|
+
}
|
|
141
|
+
// This shouldn't happen
|
|
142
|
+
if (!(maybeParameter instanceof GuAsgMinInstancesInServiceParameterExperimental)) {
|
|
143
|
+
throw new Error(`CFN parameter ${id} exists but is of type ${maybeParameter.constructor.name} instead of ${GuAsgMinInstancesInServiceParameterExperimental.name}.`);
|
|
144
|
+
}
|
|
145
|
+
return maybeParameter;
|
|
146
|
+
}
|
|
151
147
|
}
|
|
152
|
-
exports.
|
|
148
|
+
exports.GuAsgMinInstancesInServiceParameterExperimental = GuAsgMinInstancesInServiceParameterExperimental;
|
|
153
149
|
/**
|
|
154
150
|
* An IAM Policy allowing the sending of a CloudFormation signal.
|
|
155
151
|
*
|
|
@@ -412,9 +408,17 @@ class GuEc2AppExperimental extends patterns_1.GuEc2App {
|
|
|
412
408
|
buildIdentifier,
|
|
413
409
|
slowStartDuration,
|
|
414
410
|
});
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
411
|
+
/*
|
|
412
|
+
Instantiate the `GuAutoScalingRollingUpdateTimeoutExperimental` exactly once for a GuStack.
|
|
413
|
+
Whilst multiple instances are syntactically valid, it will cause ASG nodes to be incorrectly evaluated/mutated multiple times.
|
|
414
|
+
TODO Once out of experimental, instantiate these `Aspect`s directly in `GuStack`.
|
|
415
|
+
*/
|
|
416
|
+
const allAspects = aws_cdk_lib_1.Aspects.of(scope).all;
|
|
417
|
+
const maybeRollingUpdateTimeoutAspect = allAspects.find((_) => _ instanceof GuAutoScalingRollingUpdateTimeoutExperimental);
|
|
418
|
+
if (!maybeRollingUpdateTimeoutAspect) {
|
|
419
|
+
aws_cdk_lib_1.Aspects.of(scope).add(new GuAutoScalingRollingUpdateTimeoutExperimental());
|
|
420
|
+
}
|
|
421
|
+
aws_cdk_lib_1.Aspects.of(scope).add(new GuHorizontallyScalingDeploymentPropertiesExperimental());
|
|
418
422
|
}
|
|
419
423
|
}
|
|
420
424
|
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 { 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(\n Duration.parse(currentRollingUpdate.pauseTime ?? \"0\").toSeconds() + signalTimeoutSeconds,\n ).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 && construct.stack.stackName === this.stack.stackName) {\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 }),\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 * The amount of time that newly launched instances will spend warming-up / serving fewer requests than other instances.\n * The range is 30-900 seconds (15 minutes). The default is 0 seconds (disabled).\n *\n * See https://docs.aws.amazon.com/elasticloadbalancing/latest/application/edit-target-group-attributes.html#slow-start-mode\n * for more details.\n *\n * We recommend enabling this setting if you run a high-traffic service, particularly if it is JVM-based.\n *\n * Note that there is a trade-off between reliability and speed here; a longer duration will give new instances\n * more time to warm-up, but it will also slow down deployments and scale-up events.\n */\n slowStartDuration?: Duration;\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(\n maximumInstances: number,\n minimumInstances?: number,\n slowStartDuration: Duration = Duration.seconds(0),\n ) {\n return UpdatePolicy.rollingUpdate({\n maxBatchSize: maximumInstances,\n minInstancesInService: minimumInstances,\n minSuccessPercentage: 100,\n waitOnResourceSignals: true,\n /*\n All of these processes can launch and/or terminate instances and if this happens during a deployment it might\n cause the rolling update to fail: https://repost.aws/knowledge-center/auto-scaling-group-rolling-updates\n The full list of processes can be found here: https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-suspend-resume-processes.html#process-types\n */\n suspendProcesses: [\n ScalingProcess.ALARM_NOTIFICATION,\n ScalingProcess.AZ_REBALANCE,\n ScalingProcess.HEALTH_CHECK,\n ScalingProcess.INSTANCE_REFRESH,\n ScalingProcess.REPLACE_UNHEALTHY,\n ScalingProcess.SCHEDULED_ACTIONS,\n ],\n /*\n Note: this is increased via an Aspect which also takes healthcheck grace period into account.\n\n It was easier to pass the slow start duration through here rather than trying to do this via the\n GuAutoScalingRollingUpdateTimeoutExperimental aspect.\n\n If we do this via the aspect then we need to find all mappings between ASGs and target groups and then\n get the relevant slow start duration for each. This is probably possible but would be reasonably complex.\n However, it's worth mentioning that by taking this approach we open up a small risk that things will not work\n as expected if users enable slow start via a different mechanism (e.g. by modifying the target group outside\n the pattern).\n */\n pauseTime: slowStartDuration,\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 slowStartDuration?: Duration;\n}\n\nclass GuRiffRaffDeploymentIdParameter extends CfnParameter {\n private static instance: GuRiffRaffDeploymentIdParameter | undefined;\n\n private constructor(scope: GuStack) {\n super(scope, \"RiffRaffDeploymentId\", {\n type: \"String\",\n description: \"Used by Riff-Raff to inject the deployment ID.\",\n });\n }\n\n public static getInstance(stack: GuStack): GuRiffRaffDeploymentIdParameter {\n if (!this.instance || !isSingletonPresentInStack(stack, this.instance)) {\n this.instance = new GuRiffRaffDeploymentIdParameter(stack);\n }\n\n return this.instance;\n }\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, slowStartDuration } = props;\n const cfnAutoScalingGroup = autoScalingGroup.node.defaultChild as CfnAutoScalingGroup;\n\n const deploymentId = GuRiffRaffDeploymentIdParameter.getInstance(scope);\n\n // This allows redeployment of the same build to cycle instances\n autoScalingGroup.userData.addCommands(\n `echo \"Launch template last updated by Riff-Raff deployment ID: ${deploymentId.valueAsString}\"`,\n );\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} Instance Health Check 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} Instance Health Check End`,\n );\n\n if (slowStartDuration) {\n const slowStartInSeconds = slowStartDuration.toSeconds().toString();\n targetGroup.setAttribute(\"slow_start.duration_seconds\", slowStartInSeconds);\n autoScalingGroup.userData.addCommands(\n `# ${GuEc2AppExperimental.name} SlowStart Wait Period Start`,\n `\n echo \"Sleeping for ${slowStartInSeconds} seconds while instance running build ${buildIdentifier} warms up...\"\n sleep ${slowStartInSeconds}s\n echo \"Instance running build ${buildIdentifier} should have warmed up by now...\"\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 if [ \"$STATE\" == \"\\\\\"healthy\\\\\"\" ]; then\n echo \"Instance running build ${buildIdentifier} is ready to start serving a normal percentage of requests\"\n else\n echo \"Instance running build ${buildIdentifier} was not healthy after warm-up period; a failure signal will be sent\"\n exit 1\n fi\n `,\n `# ${GuEc2AppExperimental.name} SlowStart Wait Period End`,\n );\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, slowStartDuration } = props;\n\n const slowStartDurationInSeconds = slowStartDuration?.toSeconds();\n if (slowStartDurationInSeconds && (slowStartDurationInSeconds < 30 || slowStartDurationInSeconds > 900)) {\n throw new Error(\"Slow start duration must be between 30 and 900 seconds\");\n }\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, slowStartDuration),\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 slowStartDuration,\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"]}
|
|
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 { 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 * @note\n * Be sure to instantiate this only once for an entire {@link GuStack}, else the properties of the ASG will be incorrectly updated multiple times.\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 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(\n Duration.parse(currentRollingUpdate.pauseTime ?? \"0\").toSeconds() + signalTimeoutSeconds,\n ).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 * - 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 visit(construct: IConstruct) {\n if (CfnScalingPolicy.isCfnScalingPolicy(construct)) {\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 minInstancesInService = GuAsgMinInstancesInServiceParameterExperimental.getInstance(autoScalingGroup);\n\n cfnAutoScalingGroup.cfnOptions.updatePolicy = {\n autoScalingRollingUpdate: {\n ...currentRollingUpdate,\n minInstancesInService: minInstancesInService.valueAsNumber,\n },\n };\n }\n }\n }\n}\n\n/**\n * A CloudFormation parameter used to make the `MinInstancesInService` property of an ASG dynamic during deployment with Riff-Raff.\n * This shouldn't be instantiated directly, but via {@link GuHorizontallyScalingDeploymentPropertiesExperimental}.\n */\nexport class GuAsgMinInstancesInServiceParameterExperimental extends CfnParameter {\n public readonly asg: GuAutoScalingGroup;\n\n private constructor(scope: GuStack, id: string, autoScalingGroup: GuAutoScalingGroup) {\n super(scope, id, { type: \"Number\" });\n this.asg = autoScalingGroup;\n }\n\n public static getInstance(autoScalingGroup: GuAutoScalingGroup): GuAsgMinInstancesInServiceParameterExperimental {\n const stack = GuStack.of(autoScalingGroup) as GuStack;\n const { app } = autoScalingGroup;\n const id = `MinInstancesInServiceFor${app.replaceAll(\"-\", \"\")}`;\n\n const maybeParameter = stack.parameters[id];\n\n if (!maybeParameter) {\n return new GuAsgMinInstancesInServiceParameterExperimental(stack, id, autoScalingGroup);\n }\n\n // This shouldn't happen\n if (!(maybeParameter instanceof GuAsgMinInstancesInServiceParameterExperimental)) {\n throw new Error(\n `CFN parameter ${id} exists but is of type ${maybeParameter.constructor.name} instead of ${GuAsgMinInstancesInServiceParameterExperimental.name}.`,\n );\n }\n\n return maybeParameter;\n }\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 * The amount of time that newly launched instances will spend warming-up / serving fewer requests than other instances.\n * The range is 30-900 seconds (15 minutes). The default is 0 seconds (disabled).\n *\n * See https://docs.aws.amazon.com/elasticloadbalancing/latest/application/edit-target-group-attributes.html#slow-start-mode\n * for more details.\n *\n * We recommend enabling this setting if you run a high-traffic service, particularly if it is JVM-based.\n *\n * Note that there is a trade-off between reliability and speed here; a longer duration will give new instances\n * more time to warm-up, but it will also slow down deployments and scale-up events.\n */\n slowStartDuration?: Duration;\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(\n maximumInstances: number,\n minimumInstances?: number,\n slowStartDuration: Duration = Duration.seconds(0),\n ) {\n return UpdatePolicy.rollingUpdate({\n maxBatchSize: maximumInstances,\n minInstancesInService: minimumInstances,\n minSuccessPercentage: 100,\n waitOnResourceSignals: true,\n /*\n All of these processes can launch and/or terminate instances and if this happens during a deployment it might\n cause the rolling update to fail: https://repost.aws/knowledge-center/auto-scaling-group-rolling-updates\n The full list of processes can be found here: https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-suspend-resume-processes.html#process-types\n */\n suspendProcesses: [\n ScalingProcess.ALARM_NOTIFICATION,\n ScalingProcess.AZ_REBALANCE,\n ScalingProcess.HEALTH_CHECK,\n ScalingProcess.INSTANCE_REFRESH,\n ScalingProcess.REPLACE_UNHEALTHY,\n ScalingProcess.SCHEDULED_ACTIONS,\n ],\n /*\n Note: this is increased via an Aspect which also takes healthcheck grace period into account.\n\n It was easier to pass the slow start duration through here rather than trying to do this via the\n GuAutoScalingRollingUpdateTimeoutExperimental aspect.\n\n If we do this via the aspect then we need to find all mappings between ASGs and target groups and then\n get the relevant slow start duration for each. This is probably possible but would be reasonably complex.\n However, it's worth mentioning that by taking this approach we open up a small risk that things will not work\n as expected if users enable slow start via a different mechanism (e.g. by modifying the target group outside\n the pattern).\n */\n pauseTime: slowStartDuration,\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 slowStartDuration?: Duration;\n}\n\nclass GuRiffRaffDeploymentIdParameter extends CfnParameter {\n private static instance: GuRiffRaffDeploymentIdParameter | undefined;\n\n private constructor(scope: GuStack) {\n super(scope, \"RiffRaffDeploymentId\", {\n type: \"String\",\n description: \"Used by Riff-Raff to inject the deployment ID.\",\n });\n }\n\n public static getInstance(stack: GuStack): GuRiffRaffDeploymentIdParameter {\n if (!this.instance || !isSingletonPresentInStack(stack, this.instance)) {\n this.instance = new GuRiffRaffDeploymentIdParameter(stack);\n }\n\n return this.instance;\n }\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, slowStartDuration } = props;\n const cfnAutoScalingGroup = autoScalingGroup.node.defaultChild as CfnAutoScalingGroup;\n\n const deploymentId = GuRiffRaffDeploymentIdParameter.getInstance(scope);\n\n // This allows redeployment of the same build to cycle instances\n autoScalingGroup.userData.addCommands(\n `echo \"Launch template last updated by Riff-Raff deployment ID: ${deploymentId.valueAsString}\"`,\n );\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} Instance Health Check 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} Instance Health Check End`,\n );\n\n if (slowStartDuration) {\n const slowStartInSeconds = slowStartDuration.toSeconds().toString();\n targetGroup.setAttribute(\"slow_start.duration_seconds\", slowStartInSeconds);\n autoScalingGroup.userData.addCommands(\n `# ${GuEc2AppExperimental.name} SlowStart Wait Period Start`,\n `\n echo \"Sleeping for ${slowStartInSeconds} seconds while instance running build ${buildIdentifier} warms up...\"\n sleep ${slowStartInSeconds}s\n echo \"Instance running build ${buildIdentifier} should have warmed up by now...\"\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 if [ \"$STATE\" == \"\\\\\"healthy\\\\\"\" ]; then\n echo \"Instance running build ${buildIdentifier} is ready to start serving a normal percentage of requests\"\n else\n echo \"Instance running build ${buildIdentifier} was not healthy after warm-up period; a failure signal will be sent\"\n exit 1\n fi\n `,\n `# ${GuEc2AppExperimental.name} SlowStart Wait Period End`,\n );\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, slowStartDuration } = props;\n\n const slowStartDurationInSeconds = slowStartDuration?.toSeconds();\n if (slowStartDurationInSeconds && (slowStartDurationInSeconds < 30 || slowStartDurationInSeconds > 900)) {\n throw new Error(\"Slow start duration must be between 30 and 900 seconds\");\n }\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, slowStartDuration),\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 slowStartDuration,\n });\n\n /*\n Instantiate the `GuAutoScalingRollingUpdateTimeoutExperimental` exactly once for a GuStack.\n Whilst multiple instances are syntactically valid, it will cause ASG nodes to be incorrectly evaluated/mutated multiple times.\n TODO Once out of experimental, instantiate these `Aspect`s directly in `GuStack`.\n */\n const allAspects = Aspects.of(scope).all;\n const maybeRollingUpdateTimeoutAspect = allAspects.find(\n (_) => _ instanceof GuAutoScalingRollingUpdateTimeoutExperimental,\n );\n if (!maybeRollingUpdateTimeoutAspect) {\n Aspects.of(scope).add(new GuAutoScalingRollingUpdateTimeoutExperimental());\n }\n\n Aspects.of(scope).add(new GuHorizontallyScalingDeploymentPropertiesExperimental());\n }\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { GuAutoScalingGroup } from "../../constructs/autoscaling";
|
|
2
|
+
import type { GuAsgMinInstancesInServiceParameterExperimental } from "../../experimental/patterns/ec2-app";
|
|
2
3
|
import type { CdkStacksDifferingOnlyByStage, RiffRaffDeployment, RiffRaffDeploymentParameters } from "../types";
|
|
3
4
|
export declare function cloudFormationDeployment(cdkStacks: CdkStacksDifferingOnlyByStage, dependencies: RiffRaffDeployment[], contentDirectory: string): RiffRaffDeployment;
|
|
4
5
|
export declare function getAmiParameters(autoScalingGroups: GuAutoScalingGroup[]): RiffRaffDeploymentParameters;
|
|
5
|
-
export declare function getMinInstancesInServiceParameters(
|
|
6
|
+
export declare function getMinInstancesInServiceParameters(cfnParameters: Map<string, GuAsgMinInstancesInServiceParameterExperimental>): RiffRaffDeploymentParameters;
|
|
6
7
|
//# sourceMappingURL=cloudformation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudformation.d.ts","sourceRoot":"","sources":["../../../src/riff-raff-yaml-file/deployments/cloudformation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"cloudformation.d.ts","sourceRoot":"","sources":["../../../src/riff-raff-yaml-file/deployments/cloudformation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,KAAK,EAAE,+CAA+C,EAAE,MAAM,qCAAqC,CAAC;AAE3G,OAAO,KAAK,EAAE,6BAA6B,EAAE,kBAAkB,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAC;AAEhH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,6BAA6B,EACxC,YAAY,EAAE,kBAAkB,EAAE,EAClC,gBAAgB,EAAE,MAAM,GACvB,kBAAkB,CAuCpB;AAED,wBAAgB,gBAAgB,CAAC,iBAAiB,EAAE,kBAAkB,EAAE,GAAG,4BAA4B,CAwBtG;AAED,wBAAgB,kCAAkC,CAChD,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,+CAA+C,CAAC,GAC1E,4BAA4B,CAS9B"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getMinInstancesInServiceParameters = exports.getAmiParameters = exports.cloudFormationDeployment = void 0;
|
|
4
|
-
const ec2_app_1 = require("../../experimental/patterns/ec2-app");
|
|
5
4
|
const string_1 = require("../../utils/string");
|
|
6
5
|
function cloudFormationDeployment(cdkStacks, dependencies, contentDirectory) {
|
|
7
6
|
// The stacks in `cdkStacks` only differ by stage, so we can just use the first item in the list for key information.
|
|
@@ -54,17 +53,13 @@ function getAmiParameters(autoScalingGroups) {
|
|
|
54
53
|
}, {});
|
|
55
54
|
}
|
|
56
55
|
exports.getAmiParameters = getAmiParameters;
|
|
57
|
-
function getMinInstancesInServiceParameters(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
App: app,
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
}, {});
|
|
56
|
+
function getMinInstancesInServiceParameters(cfnParameters) {
|
|
57
|
+
const response = {};
|
|
58
|
+
cfnParameters.forEach((cfnParam, cfnParamName) => {
|
|
59
|
+
const app = cfnParam.asg.app;
|
|
60
|
+
response[cfnParamName] = { App: app };
|
|
61
|
+
});
|
|
62
|
+
return response;
|
|
68
63
|
}
|
|
69
64
|
exports.getMinInstancesInServiceParameters = getMinInstancesInServiceParameters;
|
|
70
65
|
//# sourceMappingURL=cloudformation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudformation.js","sourceRoot":"","sources":["../../../src/riff-raff-yaml-file/deployments/cloudformation.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"cloudformation.js","sourceRoot":"","sources":["../../../src/riff-raff-yaml-file/deployments/cloudformation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { GuAutoScalingGroup } from \"../../constructs/autoscaling\";\nimport type { GuAsgMinInstancesInServiceParameterExperimental } from \"../../experimental/patterns/ec2-app\";\nimport { toKebabCase } from \"../../utils/string\";\nimport type { CdkStacksDifferingOnlyByStage, RiffRaffDeployment, RiffRaffDeploymentParameters } from \"../types\";\n\nexport function cloudFormationDeployment(\n cdkStacks: CdkStacksDifferingOnlyByStage,\n dependencies: RiffRaffDeployment[],\n contentDirectory: string,\n): RiffRaffDeployment {\n // The stacks in `cdkStacks` only differ by stage, so we can just use the first item in the list for key information.\n const [cdkStack] = cdkStacks;\n\n if (!cdkStack) {\n throw new Error(\"Unable to produce a working riff-raff.yaml file; there are no stacks!\");\n }\n\n const {\n region,\n stack,\n constructor: { name },\n } = cdkStack;\n\n const kebabClassName = toKebabCase(name);\n\n const templateStagePaths = cdkStacks.reduce(\n (acc, { stage, templateFile }) => ({\n ...acc,\n [stage]: templateFile,\n }),\n {},\n );\n\n return {\n name: [\"cfn\", region, stack, kebabClassName].join(\"-\"),\n props: {\n type: \"cloud-formation\",\n regions: new Set([region]),\n stacks: new Set([stack]),\n app: kebabClassName,\n contentDirectory,\n parameters: {\n templateStagePaths,\n },\n // only add the `dependencies` property if there are some\n ...(dependencies.length > 0 && { dependencies: dependencies.map(({ name }) => name) }),\n },\n };\n}\n\nexport function getAmiParameters(autoScalingGroups: GuAutoScalingGroup[]): RiffRaffDeploymentParameters {\n return autoScalingGroups.reduce<RiffRaffDeploymentParameters>((acc, asg) => {\n const { imageRecipe, app, amiParameter } = asg;\n\n if (!imageRecipe) {\n throw new Error(`Unable to produce a working riff-raff.yaml file; imageRecipe missing from ASG ${app}`);\n }\n\n const Recipe = typeof imageRecipe === \"string\" ? imageRecipe : imageRecipe.Recipe;\n const AmigoStage = typeof imageRecipe === \"string\" ? \"PROD\" : (imageRecipe.AmigoStage ?? \"PROD\");\n\n // In YAML `true` is a Boolean. Riff-Raff expects a String here, so call `toString` to make it `'true'`.\n const Encrypted = (typeof imageRecipe === \"string\" ? true : (imageRecipe.Encrypted ?? true)).toString();\n\n return {\n ...acc,\n [amiParameter.node.id]: {\n BuiltBy: \"amigo\",\n AmigoStage,\n Recipe,\n Encrypted,\n },\n };\n }, {});\n}\n\nexport function getMinInstancesInServiceParameters(\n cfnParameters: Map<string, GuAsgMinInstancesInServiceParameterExperimental>,\n): RiffRaffDeploymentParameters {\n const response: RiffRaffDeploymentParameters = {};\n\n cfnParameters.forEach((cfnParam, cfnParamName) => {\n const app = cfnParam.asg.app;\n response[cfnParamName] = { App: app };\n });\n\n return response;\n}\n"]}
|