@guardian/cdk 48.1.0 → 49.0.0-beta.1
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/aspects/cfn-include-reporter.d.ts +9 -0
- package/lib/aspects/cfn-include-reporter.d.ts.map +1 -0
- package/lib/aspects/cfn-include-reporter.js +18 -0
- package/lib/aspects/cfn-include-reporter.js.map +1 -0
- package/lib/constructs/core/stack.d.ts.map +1 -1
- package/lib/constructs/core/stack.js +2 -14
- package/lib/constructs/core/stack.js.map +1 -1
- package/lib/experimental/constructs/error-budget-alarm.d.ts +12 -0
- package/lib/experimental/constructs/error-budget-alarm.d.ts.map +1 -0
- package/lib/experimental/constructs/error-budget-alarm.js +106 -0
- package/lib/experimental/constructs/error-budget-alarm.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IAspect } from "aws-cdk-lib";
|
|
2
|
+
import type { IConstruct } from "constructs";
|
|
3
|
+
/**
|
|
4
|
+
* An Aspect to remind users to check for stateful resources when migrating a YAML/JSON template.
|
|
5
|
+
*/
|
|
6
|
+
export declare class CfnIncludeReporter implements IAspect {
|
|
7
|
+
visit(node: IConstruct): void;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=cfn-include-reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cfn-include-reporter.d.ts","sourceRoot":"","sources":["../../src/aspects/cfn-include-reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C;;GAEG;AACH,qBAAa,kBAAmB,YAAW,OAAO;IACzC,KAAK,CAAC,IAAI,EAAE,UAAU;CAO9B"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CfnIncludeReporter = void 0;
|
|
4
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
5
|
+
const cloudformation_include_1 = require("aws-cdk-lib/cloudformation-include");
|
|
6
|
+
const constants_1 = require("../constants");
|
|
7
|
+
/**
|
|
8
|
+
* An Aspect to remind users to check for stateful resources when migrating a YAML/JSON template.
|
|
9
|
+
*/
|
|
10
|
+
class CfnIncludeReporter {
|
|
11
|
+
visit(node) {
|
|
12
|
+
if (node instanceof cloudformation_include_1.CfnInclude) {
|
|
13
|
+
aws_cdk_lib_1.Annotations.of(node).addWarning(`As you're migrating a YAML/JSON template to ${constants_1.LibraryInfo.NAME}, be sure to check for any stateful resources! See https://github.com/guardian/cdk/blob/main/docs/stateful-resources.md.`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.CfnIncludeReporter = CfnIncludeReporter;
|
|
18
|
+
//# sourceMappingURL=cfn-include-reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cfn-include-reporter.js","sourceRoot":"","sources":["../../src/aspects/cfn-include-reporter.ts"],"names":[],"mappings":";;;AACA,6CAA0C;AAC1C,+EAAgE;AAEhE,4CAA2C;AAE3C;;GAEG;AACH,MAAa,kBAAkB;IACtB,KAAK,CAAC,IAAgB;QAC3B,IAAI,IAAI,YAAY,mCAAU,EAAE;YAC9B,yBAAW,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,UAAU,CAC7B,+CAA+C,uBAAW,CAAC,IAAI,0HAA0H,CAC1L,CAAC;SACH;IACH,CAAC;CACF;AARD,gDAQC","sourcesContent":["import type { IAspect } from \"aws-cdk-lib\";\nimport { Annotations } from \"aws-cdk-lib\";\nimport { CfnInclude } from \"aws-cdk-lib/cloudformation-include\";\nimport type { IConstruct } from \"constructs\";\nimport { LibraryInfo } from \"../constants\";\n\n/**\n * An Aspect to remind users to check for stateful resources when migrating a YAML/JSON template.\n */\nexport class CfnIncludeReporter implements IAspect {\n public visit(node: IConstruct) {\n if (node instanceof CfnInclude) {\n Annotations.of(node).addWarning(\n `As you're migrating a YAML/JSON template to ${LibraryInfo.NAME}, be sure to check for any stateful resources! See https://github.com/guardian/cdk/blob/main/docs/stateful-resources.md.`\n );\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../../../src/constructs/core/stack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,GAAG,EAAW,YAAY,EAA0B,KAAK,EAAQ,MAAM,aAAa,CAAC;AAC3G,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../../../src/constructs/core/stack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,GAAG,EAAW,YAAY,EAA0B,KAAK,EAAQ,MAAM,aAAa,CAAC;AAC3G,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C,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,KAAK,EAAE,MAAM,CAAC;IAEd;;;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,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED;;;;;;;;;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,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY;IA+BrD;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAa3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqDG;IACI,iBAAiB,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,iBAAiB,GAAG,IAAI;CAQhG;AAED;;;;GAIG;AACH,qBAAa,0BAA2B,SAAQ,OAAO;gBAEzC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY;IAI3C,IAAI,OAAO,IAAI,MAAM,CAEpB;CACF"}
|
|
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.GuStackForStackSetInstance = exports.GuStack = void 0;
|
|
7
7
|
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
8
8
|
const assertions_1 = require("aws-cdk-lib/assertions");
|
|
9
|
-
const cloudformation_include_1 = require("aws-cdk-lib/cloudformation-include");
|
|
10
9
|
const git_url_parse_1 = __importDefault(require("git-url-parse"));
|
|
10
|
+
const cfn_include_reporter_1 = require("../../aspects/cfn-include-reporter");
|
|
11
11
|
const metadata_1 = require("../../aspects/metadata");
|
|
12
12
|
const constants_1 = require("../../constants");
|
|
13
13
|
const git_1 = require("../../utils/git");
|
|
@@ -60,19 +60,7 @@ class GuStack extends aws_cdk_lib_1.Stack {
|
|
|
60
60
|
if (!props.withoutMetadata) {
|
|
61
61
|
aws_cdk_lib_1.Aspects.of(this).add(new metadata_1.Metadata(this));
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
node.addValidation({
|
|
65
|
-
validate() {
|
|
66
|
-
const NO_ERRORS = [];
|
|
67
|
-
node
|
|
68
|
-
.findAll()
|
|
69
|
-
.filter((construct) => construct instanceof cloudformation_include_1.CfnInclude)
|
|
70
|
-
.map((cfnInclude) => {
|
|
71
|
-
aws_cdk_lib_1.Annotations.of(cfnInclude).addWarning(`As you're migrating a YAML/JSON template to ${constants_1.LibraryInfo.NAME}, be sure to check for any stateful resources! See https://github.com/guardian/cdk/blob/main/docs/stateful-resources.md.`);
|
|
72
|
-
});
|
|
73
|
-
return NO_ERRORS;
|
|
74
|
-
},
|
|
75
|
-
});
|
|
63
|
+
aws_cdk_lib_1.Aspects.of(this).add(new cfn_include_reporter_1.CfnIncludeReporter());
|
|
76
64
|
}
|
|
77
65
|
get stage() {
|
|
78
66
|
return this._stage;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack.js","sourceRoot":"","sources":["../../../src/constructs/core/stack.ts"],"names":[],"mappings":"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","sourcesContent":["import { Annotations, App, Aspects, CfnParameter, LegacyStackSynthesizer, Stack, Tags } from \"aws-cdk-lib\";\nimport type { CfnElement, StackProps } from \"aws-cdk-lib\";\nimport { Template } from \"aws-cdk-lib/assertions\";\nimport { CfnInclude } from \"aws-cdk-lib/cloudformation-include\";\nimport type { IConstruct } from \"constructs\";\nimport gitUrlParse from \"git-url-parse\";\nimport { Metadata } from \"../../aspects/metadata\";\nimport { ContextKeys, LibraryInfo, 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 stage: 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 private readonly _stack: string;\n private readonly _stage: string;\n\n get stage(): string {\n return this._stage;\n }\n\n get stack(): string {\n return this._stack;\n }\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(app: App, id: string, props: GuStackProps) {\n const { cloudFormationStackName = process.env.GU_CFN_STACK_NAME, stack, stage, withoutTags } = props;\n\n super(app, 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\n if (!withoutTags) {\n this.addTag(TrackingTag.Key, TrackingTag.Value);\n\n this.addTag(\"Stack\", this.stack);\n this.addTag(\"Stage\", this.stage);\n\n this.tryAddRepositoryTag();\n }\n\n if (!props.withoutMetadata) {\n Aspects.of(this).add(new Metadata(this));\n }\n\n const { node } = this;\n\n node.addValidation({\n validate(): string[] {\n const NO_ERRORS: string[] = [];\n\n node\n .findAll()\n .filter((construct) => construct instanceof CfnInclude)\n .map((cfnInclude) => {\n Annotations.of(cfnInclude).addWarning(\n `As you're migrating a YAML/JSON template to ${LibraryInfo.NAME}, be sure to check for any stateful resources! See https://github.com/guardian/cdk/blob/main/docs/stateful-resources.md.`\n );\n });\n\n return NO_ERRORS;\n },\n });\n }\n\n /**\n * Adds a tag to resources in the stack for 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 tryAddRepositoryTag(): void {\n try {\n const urlFromContext = this.node.tryGetContext(ContextKeys.REPOSITORY_URL) as string | undefined;\n const repositoryUrl: string = urlFromContext ?? gitRemoteOriginUrl();\n const repositoryName = gitUrlParse(repositoryUrl).full_name;\n this.addTag(MetadataKeys.REPOSITORY_NAME, repositoryName);\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 }\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\n/**\n * A GuStack but designed for Stack Set instances.\n *\n * In a stack set application, `GuStackForStackSetInstance` is used to represent the infrastructure to provision in target AWS accounts.\n */\nexport class GuStackForStackSetInstance extends GuStack {\n // eslint-disable-next-line custom-rules/valid-constructors -- GuStackForStackSet should have a unique `App`\n constructor(id: string, props: GuStackProps) {\n super(new App(), id, props);\n }\n\n get cfnJson(): string {\n return JSON.stringify(Template.fromStack(this).toJSON(), null, 2);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"stack.js","sourceRoot":"","sources":["../../../src/constructs/core/stack.ts"],"names":[],"mappings":"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","sourcesContent":["import { Annotations, App, Aspects, CfnParameter, LegacyStackSynthesizer, Stack, Tags } from \"aws-cdk-lib\";\nimport type { CfnElement, StackProps } from \"aws-cdk-lib\";\nimport { Template } from \"aws-cdk-lib/assertions\";\nimport type { IConstruct } from \"constructs\";\nimport gitUrlParse from \"git-url-parse\";\nimport { CfnIncludeReporter } from \"../../aspects/cfn-include-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 stage: 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 private readonly _stack: string;\n private readonly _stage: string;\n\n get stage(): string {\n return this._stage;\n }\n\n get stack(): string {\n return this._stack;\n }\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(app: App, id: string, props: GuStackProps) {\n const { cloudFormationStackName = process.env.GU_CFN_STACK_NAME, stack, stage, withoutTags } = props;\n\n super(app, 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\n if (!withoutTags) {\n this.addTag(TrackingTag.Key, TrackingTag.Value);\n\n this.addTag(\"Stack\", this.stack);\n this.addTag(\"Stage\", this.stage);\n\n this.tryAddRepositoryTag();\n }\n\n if (!props.withoutMetadata) {\n Aspects.of(this).add(new Metadata(this));\n }\n\n Aspects.of(this).add(new CfnIncludeReporter());\n }\n\n /**\n * Adds a tag to resources in the stack for 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 tryAddRepositoryTag(): void {\n try {\n const urlFromContext = this.node.tryGetContext(ContextKeys.REPOSITORY_URL) as string | undefined;\n const repositoryUrl: string = urlFromContext ?? gitRemoteOriginUrl();\n const repositoryName = gitUrlParse(repositoryUrl).full_name;\n this.addTag(MetadataKeys.REPOSITORY_NAME, repositoryName);\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 }\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\n/**\n * A GuStack but designed for Stack Set instances.\n *\n * In a stack set application, `GuStackForStackSetInstance` is used to represent the infrastructure to provision in target AWS accounts.\n */\nexport class GuStackForStackSetInstance extends GuStack {\n // eslint-disable-next-line custom-rules/valid-constructors -- GuStackForStackSet should have a unique `App`\n constructor(id: string, props: GuStackProps) {\n super(new App(), id, props);\n }\n\n get cfnJson(): string {\n return JSON.stringify(Template.fromStack(this).toJSON(), null, 2);\n }\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IMetric } from "aws-cdk-lib/aws-cloudwatch";
|
|
2
|
+
import type { GuStack } from "../../constructs/core";
|
|
3
|
+
export interface ErrorBudgetAlarmProps {
|
|
4
|
+
snsTopicNameForAlerts: string;
|
|
5
|
+
sloTarget: number;
|
|
6
|
+
badEvents: IMetric;
|
|
7
|
+
validEvents: IMetric;
|
|
8
|
+
}
|
|
9
|
+
export declare class ErrorBudgetAlarmExperimental {
|
|
10
|
+
constructor(scope: GuStack, props: ErrorBudgetAlarmProps);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=error-budget-alarm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-budget-alarm.d.ts","sourceRoot":"","sources":["../../../src/experimental/constructs/error-budget-alarm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EAAC,MAAM,4BAA4B,CAAC;AAW7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;CAEtB;AAwCD,qBAAa,4BAA4B;gBAE3B,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,qBAAqB;CAqEzD"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ErrorBudgetAlarmExperimental = void 0;
|
|
4
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
5
|
+
const aws_cloudwatch_1 = require("aws-cdk-lib/aws-cloudwatch");
|
|
6
|
+
const aws_cloudwatch_actions_1 = require("aws-cdk-lib/aws-cloudwatch-actions");
|
|
7
|
+
const aws_sns_1 = require("aws-cdk-lib/aws-sns");
|
|
8
|
+
function applySuppression(alarmToSuppress, suppressingAlarm, shortWindow) {
|
|
9
|
+
const cfnAlarm = alarmToSuppress.node.defaultChild;
|
|
10
|
+
cfnAlarm.actionsSuppressor = suppressingAlarm.alarmArn;
|
|
11
|
+
cfnAlarm.actionsSuppressorWaitPeriod = 120;
|
|
12
|
+
cfnAlarm.actionsSuppressorExtensionPeriod = shortWindow.toSeconds();
|
|
13
|
+
}
|
|
14
|
+
// Do we need to extend construct here?
|
|
15
|
+
class ErrorBudgetAlarmExperimental {
|
|
16
|
+
constructor(scope, props) {
|
|
17
|
+
const errorBudget = 1 - props.sloTarget;
|
|
18
|
+
const snsTopic = aws_sns_1.Topic.fromTopicArn(scope, 'SnsSloAlarms', `arn:aws:sns:${scope.region}:${scope.account}:${props.snsTopicNameForAlerts}`);
|
|
19
|
+
const fastBurnRate = {
|
|
20
|
+
speed: 'Fast',
|
|
21
|
+
burnRate: 15,
|
|
22
|
+
longWindow: aws_cdk_lib_1.Duration.hours(1),
|
|
23
|
+
shortWindow: aws_cdk_lib_1.Duration.minutes(5),
|
|
24
|
+
};
|
|
25
|
+
const fastBurnAlarm = new CompositeBurnRateAlarm(scope, {
|
|
26
|
+
badEvents: props.badEvents,
|
|
27
|
+
burnRate: fastBurnRate,
|
|
28
|
+
errorBudget,
|
|
29
|
+
snsTopic,
|
|
30
|
+
validEvents: props.validEvents,
|
|
31
|
+
});
|
|
32
|
+
const moderateBurnRate = {
|
|
33
|
+
speed: 'Moderate',
|
|
34
|
+
burnRate: 6,
|
|
35
|
+
longWindow: aws_cdk_lib_1.Duration.hours(6),
|
|
36
|
+
shortWindow: aws_cdk_lib_1.Duration.minutes(30),
|
|
37
|
+
};
|
|
38
|
+
const moderateBurnAlarm = new CompositeBurnRateAlarm(scope, {
|
|
39
|
+
badEvents: props.badEvents,
|
|
40
|
+
burnRate: moderateBurnRate,
|
|
41
|
+
errorBudget,
|
|
42
|
+
snsTopic,
|
|
43
|
+
validEvents: props.validEvents,
|
|
44
|
+
});
|
|
45
|
+
applySuppression(moderateBurnAlarm, fastBurnAlarm, moderateBurnRate.shortWindow);
|
|
46
|
+
const slowBurnRate = {
|
|
47
|
+
speed: 'Slow',
|
|
48
|
+
burnRate: 3,
|
|
49
|
+
longWindow: aws_cdk_lib_1.Duration.days(1),
|
|
50
|
+
shortWindow: aws_cdk_lib_1.Duration.hours(2),
|
|
51
|
+
};
|
|
52
|
+
const slowBurnAlarm = new CompositeBurnRateAlarm(scope, {
|
|
53
|
+
badEvents: props.badEvents,
|
|
54
|
+
burnRate: slowBurnRate,
|
|
55
|
+
errorBudget,
|
|
56
|
+
snsTopic,
|
|
57
|
+
validEvents: props.validEvents,
|
|
58
|
+
});
|
|
59
|
+
applySuppression(slowBurnAlarm, moderateBurnAlarm, slowBurnRate.shortWindow);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.ErrorBudgetAlarmExperimental = ErrorBudgetAlarmExperimental;
|
|
63
|
+
class IndividualBurnRateAlarm extends aws_cloudwatch_1.Alarm {
|
|
64
|
+
constructor(scope, props) {
|
|
65
|
+
const undesirableErrorBudgetConsumption = props.errorBudget * props.burnRateSpeed.burnRate;
|
|
66
|
+
super(scope, `${props.burnRateSpeed.speed}BurnOver${props.window.toHumanString()}`, {
|
|
67
|
+
metric: new aws_cloudwatch_1.MathExpression({
|
|
68
|
+
expression: `badEvents/validEvents`,
|
|
69
|
+
label: 'Observed failure rate',
|
|
70
|
+
usingMetrics: {
|
|
71
|
+
badEvents: props.badEvents,
|
|
72
|
+
validEvents: props.validEvents,
|
|
73
|
+
},
|
|
74
|
+
period: props.window,
|
|
75
|
+
}),
|
|
76
|
+
evaluationPeriods: 1,
|
|
77
|
+
threshold: undesirableErrorBudgetConsumption,
|
|
78
|
+
comparisonOperator: aws_cloudwatch_1.ComparisonOperator.GREATER_THAN_THRESHOLD,
|
|
79
|
+
treatMissingData: aws_cloudwatch_1.TreatMissingData.NOT_BREACHING,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
class CompositeBurnRateAlarm extends aws_cloudwatch_1.CompositeAlarm {
|
|
84
|
+
constructor(scope, props) {
|
|
85
|
+
const sharedIndividualAlarmProps = {
|
|
86
|
+
badEvents: props.badEvents,
|
|
87
|
+
burnRateSpeed: {
|
|
88
|
+
speed: props.burnRate.speed,
|
|
89
|
+
burnRate: props.burnRate.burnRate,
|
|
90
|
+
},
|
|
91
|
+
errorBudget: props.errorBudget,
|
|
92
|
+
validEvents: props.validEvents
|
|
93
|
+
};
|
|
94
|
+
super(scope, `${props.burnRate.speed}BurnCompositeAlarm`, {
|
|
95
|
+
alarmRule: aws_cloudwatch_1.AlarmRule.allOf(aws_cloudwatch_1.AlarmRule.fromAlarm(new IndividualBurnRateAlarm(scope, {
|
|
96
|
+
...sharedIndividualAlarmProps,
|
|
97
|
+
window: props.burnRate.longWindow,
|
|
98
|
+
}), aws_cloudwatch_1.AlarmState.ALARM), aws_cloudwatch_1.AlarmRule.fromAlarm(new IndividualBurnRateAlarm(scope, {
|
|
99
|
+
...sharedIndividualAlarmProps,
|
|
100
|
+
window: props.burnRate.shortWindow,
|
|
101
|
+
}), aws_cloudwatch_1.AlarmState.ALARM)),
|
|
102
|
+
});
|
|
103
|
+
this.addAlarmAction(new aws_cloudwatch_actions_1.SnsAction(props.snsTopic));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=error-budget-alarm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-budget-alarm.js","sourceRoot":"","sources":["../../../src/experimental/constructs/error-budget-alarm.ts"],"names":[],"mappings":"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","sourcesContent":["import { Duration } from \"aws-cdk-lib\";\nimport type { CfnCompositeAlarm,\n IMetric} from \"aws-cdk-lib/aws-cloudwatch\";\nimport {\n Alarm, AlarmRule, AlarmState,\n ComparisonOperator,\n CompositeAlarm,\n MathExpression,\n TreatMissingData\n} from \"aws-cdk-lib/aws-cloudwatch\";\nimport { SnsAction } from \"aws-cdk-lib/aws-cloudwatch-actions\";\nimport type { ITopic} from \"aws-cdk-lib/aws-sns\";\nimport { Topic } from \"aws-cdk-lib/aws-sns\";\nimport type { GuStack } from \"../../constructs/core\";\n\nexport interface ErrorBudgetAlarmProps {\n snsTopicNameForAlerts: string;\n sloTarget: number;\n badEvents: IMetric; // Should this be IMetric[]?\n validEvents: IMetric; // Should this be IMetric[]?\n // Could accept a MathExpression for more complicated cases?\n}\n\ninterface BurnRateSpeed {\n speed: 'Fast' | 'Moderate' | 'Slow';\n burnRate: number;\n}\n\ninterface BurnRate extends BurnRateSpeed {\n longWindow: Duration;\n shortWindow: Duration;\n}\n\ninterface IndividualBurnRateAlarmProps {\n burnRateSpeed: BurnRateSpeed;\n window: Duration;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n}\n\ninterface CompositeBurnRateAlarmProps {\n burnRate: BurnRate;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n snsTopic: ITopic;\n}\n\nfunction applySuppression(\n alarmToSuppress: CompositeAlarm,\n suppressingAlarm: CompositeAlarm,\n shortWindow: Duration,\n) {\n const cfnAlarm = alarmToSuppress.node.defaultChild as CfnCompositeAlarm;\n cfnAlarm.actionsSuppressor = suppressingAlarm.alarmArn;\n cfnAlarm.actionsSuppressorWaitPeriod = 120;\n cfnAlarm.actionsSuppressorExtensionPeriod = shortWindow.toSeconds();\n}\n\n// Do we need to extend construct here?\nexport class ErrorBudgetAlarmExperimental {\n\n constructor(scope: GuStack, props: ErrorBudgetAlarmProps) {\n\n const errorBudget = 1 - props.sloTarget;\n\n const snsTopic: ITopic = Topic.fromTopicArn(\n scope,\n 'SnsSloAlarms',\n `arn:aws:sns:${scope.region}:${scope.account}:${props.snsTopicNameForAlerts}`,\n );\n\n const fastBurnRate: BurnRate = {\n speed: 'Fast',\n burnRate: 15,\n longWindow: Duration.hours(1),\n shortWindow: Duration.minutes(5),\n };\n\n const fastBurnAlarm = new CompositeBurnRateAlarm(scope, {\n badEvents: props.badEvents,\n burnRate: fastBurnRate,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n });\n\n const moderateBurnRate: BurnRate = {\n speed: 'Moderate',\n burnRate: 6,\n longWindow: Duration.hours(6),\n shortWindow: Duration.minutes(30),\n };\n\n const moderateBurnAlarm = new CompositeBurnRateAlarm(scope, {\n badEvents: props.badEvents,\n burnRate: moderateBurnRate,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n });\n\n applySuppression(\n moderateBurnAlarm,\n fastBurnAlarm,\n moderateBurnRate.shortWindow,\n );\n\n const slowBurnRate: BurnRate = {\n speed: 'Slow',\n burnRate: 3,\n longWindow: Duration.days(1),\n shortWindow: Duration.hours(2),\n };\n\n const slowBurnAlarm = new CompositeBurnRateAlarm(scope, {\n badEvents: props.badEvents,\n burnRate: slowBurnRate,\n errorBudget,\n snsTopic, // TODO - use a different topic for tickets or figure out how to route notifications vs tickets using a single topic\n validEvents: props.validEvents,\n });\n\n applySuppression(\n slowBurnAlarm,\n moderateBurnAlarm,\n slowBurnRate.shortWindow,\n );\n\n }\n\n}\n\nclass IndividualBurnRateAlarm extends Alarm {\n constructor(scope: GuStack, props: IndividualBurnRateAlarmProps) {\n const undesirableErrorBudgetConsumption = props.errorBudget * props.burnRateSpeed.burnRate;\n super(\n scope,\n `${props.burnRateSpeed.speed}BurnOver${props.window.toHumanString()}`,\n {\n metric: new MathExpression({\n expression: `badEvents/validEvents`,\n label: 'Observed failure rate',\n usingMetrics: {\n badEvents: props.badEvents,\n validEvents: props.validEvents,\n },\n period: props.window,\n }),\n evaluationPeriods: 1,\n threshold: undesirableErrorBudgetConsumption,\n comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,\n treatMissingData: TreatMissingData.NOT_BREACHING,\n },\n );\n }\n}\n\nclass CompositeBurnRateAlarm extends CompositeAlarm {\n constructor(scope: GuStack, props: CompositeBurnRateAlarmProps) {\n const sharedIndividualAlarmProps = {\n badEvents: props.badEvents,\n burnRateSpeed: {\n speed: props.burnRate.speed,\n burnRate: props.burnRate.burnRate,\n },\n errorBudget: props.errorBudget,\n validEvents: props.validEvents\n };\n super(scope,\n `${props.burnRate.speed}BurnCompositeAlarm`,\n {\n alarmRule: AlarmRule.allOf(\n AlarmRule.fromAlarm(\n new IndividualBurnRateAlarm(scope, {\n ...sharedIndividualAlarmProps,\n window: props.burnRate.longWindow,\n }),\n AlarmState.ALARM,\n ),\n AlarmRule.fromAlarm(\n new IndividualBurnRateAlarm(scope, {\n ...sharedIndividualAlarmProps,\n window: props.burnRate.shortWindow,\n }),\n AlarmState.ALARM,\n ),\n ),\n },\n );\n this.addAlarmAction(new SnsAction(props.snsTopic));\n }\n}\n"]}
|