@guardian/cdk 48.5.0-beta.1 → 48.5.1-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/constructs/core/stack.d.ts +7 -1
- package/lib/constructs/core/stack.d.ts.map +1 -1
- package/lib/constructs/core/stack.js +8 -2
- package/lib/constructs/core/stack.js.map +1 -1
- package/lib/experimental/constructs/error-budget-alarm.d.ts.map +1 -1
- package/lib/experimental/constructs/error-budget-alarm.js +24 -25
- package/lib/experimental/constructs/error-budget-alarm.js.map +1 -1
- package/package.json +1 -1
|
@@ -10,6 +10,10 @@ export interface GuStackProps extends Omit<StackProps, "stackName"> {
|
|
|
10
10
|
*/
|
|
11
11
|
stack: string;
|
|
12
12
|
stage: string;
|
|
13
|
+
/**
|
|
14
|
+
* Optional name of the app. If defined, all resources will have an App tag.
|
|
15
|
+
*/
|
|
16
|
+
app?: string;
|
|
13
17
|
/**
|
|
14
18
|
* The AWS CloudFormation stack name (as shown in the AWS CloudFormation UI).
|
|
15
19
|
* @defaultValue the `GU_CFN_STACK_NAME` environment variable
|
|
@@ -57,8 +61,10 @@ export interface GuStackProps extends Omit<StackProps, "stackName"> {
|
|
|
57
61
|
export declare class GuStack extends Stack implements StackStageIdentity {
|
|
58
62
|
private readonly _stack;
|
|
59
63
|
private readonly _stage;
|
|
64
|
+
private readonly _app?;
|
|
60
65
|
get stage(): string;
|
|
61
66
|
get stack(): string;
|
|
67
|
+
get app(): string | undefined;
|
|
62
68
|
/**
|
|
63
69
|
* A helper function to add a tag to all resources in a stack.
|
|
64
70
|
*
|
|
@@ -76,7 +82,7 @@ export declare class GuStack extends Stack implements StackStageIdentity {
|
|
|
76
82
|
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html
|
|
77
83
|
*/
|
|
78
84
|
get parameters(): Record<string, CfnParameter>;
|
|
79
|
-
constructor(
|
|
85
|
+
constructor(scope: App, id: string, props: GuStackProps);
|
|
80
86
|
/**
|
|
81
87
|
* Adds a tag to resources in the stack for the repository name.
|
|
82
88
|
* The value is retrieved in the following order:
|
|
@@ -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;AAE1D,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,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;
|
|
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;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,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,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAS;IAE/B,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,GAAG,IAAI,MAAM,GAAG,SAAS,CAE5B;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,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY;IAmCvD;;;;;;;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"}
|
|
@@ -41,9 +41,9 @@ const git_1 = require("../../utils/git");
|
|
|
41
41
|
*/
|
|
42
42
|
class GuStack extends aws_cdk_lib_1.Stack {
|
|
43
43
|
// eslint-disable-next-line custom-rules/valid-constructors -- GuStack is the exception as it must take an App
|
|
44
|
-
constructor(
|
|
44
|
+
constructor(scope, id, props) {
|
|
45
45
|
const { cloudFormationStackName = process.env.GU_CFN_STACK_NAME, stack, stage, withoutTags } = props;
|
|
46
|
-
super(
|
|
46
|
+
super(scope, id, {
|
|
47
47
|
...props,
|
|
48
48
|
stackName: cloudFormationStackName,
|
|
49
49
|
// TODO Use `DefaultStackSynthesizer` or create own synthesizer?
|
|
@@ -56,6 +56,9 @@ class GuStack extends aws_cdk_lib_1.Stack {
|
|
|
56
56
|
this.addTag(constants_1.TrackingTag.Key, constants_1.TrackingTag.Value);
|
|
57
57
|
this.addTag("Stack", this.stack);
|
|
58
58
|
this.addTag("Stage", this.stage);
|
|
59
|
+
if (this.app) {
|
|
60
|
+
this.addTag("App", this.app);
|
|
61
|
+
}
|
|
59
62
|
this.tryAddRepositoryTag();
|
|
60
63
|
}
|
|
61
64
|
if (!props.withoutMetadata) {
|
|
@@ -70,6 +73,9 @@ class GuStack extends aws_cdk_lib_1.Stack {
|
|
|
70
73
|
get stack() {
|
|
71
74
|
return this._stack;
|
|
72
75
|
}
|
|
76
|
+
get app() {
|
|
77
|
+
return this._app;
|
|
78
|
+
}
|
|
73
79
|
/**
|
|
74
80
|
* A helper function to add a tag to all resources in a stack.
|
|
75
81
|
*
|
|
@@ -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 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 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 Aspects.of(this).add(new CfnParameterReporter());\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 { 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 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 private readonly _stack: string;\n private readonly _stage: string;\n private readonly _app?: string;\n\n get stage(): string {\n return this._stage;\n }\n\n get stack(): string {\n return this._stack;\n }\n\n get app(): string | undefined {\n return this._app;\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(scope: App, id: string, props: GuStackProps) {\n const { cloudFormationStackName = process.env.GU_CFN_STACK_NAME, stack, stage, 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\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 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 Aspects.of(this).add(new CfnParameterReporter());\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-budget-alarm.d.ts","sourceRoot":"","sources":["../../../src/experimental/constructs/error-budget-alarm.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAqB,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAa7E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,+BAA+B;IAC9C;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,+BAA+B;IAC9C;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAsCD,qBAAa,wCAAyC,SAAQ,sBAAsB;gBACtE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,+BAA+B;
|
|
1
|
+
{"version":3,"file":"error-budget-alarm.d.ts","sourceRoot":"","sources":["../../../src/experimental/constructs/error-budget-alarm.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAqB,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAa7E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,+BAA+B;IAC9C;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,+BAA+B;IAC9C;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAsCD,qBAAa,wCAAyC,SAAQ,sBAAsB;gBACtE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,+BAA+B;CAsFnE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,8BAA+B,SAAQ,SAAS;gBAC/C,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,+BAA+B;CAsEnE"}
|
|
@@ -47,31 +47,30 @@ class GuPrometheusErrorBudgetAlarmExperimental extends aws_aps_1.CfnRuleGroupsNa
|
|
|
47
47
|
const slowBurnThreshold = errorBudget * slowBurnRate.burnRate;
|
|
48
48
|
// Currently we assume that the job label refers to a unique app. This works fine with our simple test data but will not scale out properly.
|
|
49
49
|
// Perhaps we will need to sum by (${props.app}, ${scope.stack}, ${scope.stage}) in order to scale up?
|
|
50
|
-
const prometheusRules = `
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
`;
|
|
50
|
+
const prometheusRules = `groups:
|
|
51
|
+
- name: multiwindow_recording_rules
|
|
52
|
+
rules:
|
|
53
|
+
- record: ${fastBurnShortWindowRule}
|
|
54
|
+
expr: sum by (job) (rate(${props.badEvents}[${fastBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${fastBurnRate.shortWindow}]))
|
|
55
|
+
- record: ${mediumBurnShortWindowRule}
|
|
56
|
+
expr: sum by (job) (rate(${props.badEvents}[${mediumBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${mediumBurnRate.shortWindow}]))
|
|
57
|
+
- record: ${fastBurnLongWindowRule}
|
|
58
|
+
expr: sum by (job) (rate(${props.badEvents}[${fastBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${fastBurnRate.longWindow}]))
|
|
59
|
+
- record: ${slowBurnShortWindowRule}
|
|
60
|
+
expr: sum by (job) (rate(${props.badEvents}[${slowBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${slowBurnRate.shortWindow}]))
|
|
61
|
+
- record: ${mediumBurnLongWindowRule}
|
|
62
|
+
expr: sum by (job) (rate(${props.badEvents}[${mediumBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${mediumBurnRate.longWindow}]))
|
|
63
|
+
- record: ${slowBurnLongWindowRule}
|
|
64
|
+
expr: sum by (job) (rate(${props.badEvents}[${slowBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${slowBurnRate.longWindow}]))
|
|
65
|
+
- name: multiwindow_alerts
|
|
66
|
+
rules:
|
|
67
|
+
- alert: FastBurnAlarm
|
|
68
|
+
expr: (${fastBurnShortWindowRule} > ${fastBurnThreshold}) and (${fastBurnLongWindowRule} > ${fastBurnThreshold})
|
|
69
|
+
- alert: MediumBurnAlarm
|
|
70
|
+
expr: (${mediumBurnShortWindowRule} > ${mediumBurnThreshold}) and (${mediumBurnLongWindowRule} > ${mediumBurnThreshold})
|
|
71
|
+
- alert: SlowBurnAlarm
|
|
72
|
+
expr: (${slowBurnShortWindowRule} > ${slowBurnThreshold}) and (${slowBurnLongWindowRule} > ${slowBurnThreshold})
|
|
73
|
+
`;
|
|
75
74
|
// TODO: figure out how to do alert silencing for lower priority alerts - presumably via a separate config file?
|
|
76
75
|
super(scope, props.sloName, {
|
|
77
76
|
// TODO: it would probably be better to get this ARN from parameter store
|
|
@@ -1 +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 { CfnRuleGroupsNamespace } from \"aws-cdk-lib/aws-aps\";\nimport type { CfnCompositeAlarm, IMetric } from \"aws-cdk-lib/aws-cloudwatch\";\nimport {\n Alarm,\n AlarmRule,\n 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 { Construct } from \"constructs\";\nimport type { GuStack } from \"../../constructs/core\";\n\nexport interface CloudWatchErrorBudgetAlarmProps {\n /**\n * The name of the SLO. Assumed to be unique per stack.\n */\n sloName: string;\n /**\n * The percentange target for the SLO, expressed as a decimal floating point number. Must be between 0.95 and 0.995.\n */\n sloTarget: number;\n /**\n * The metric representing the bad events. Can be any IMetric type, like Metric or MathExpression.\n */\n badEvents: IMetric;\n /**\n * The metric representing the events to be considered. Can be any IMetric type, like Metric or MathExpression.\n */\n validEvents: IMetric;\n /**\n * The sns topic name (not the full arn) used by the alarm to send email alerts.\n */\n snsTopicNameForAlerts: string;\n}\n\nexport interface PrometheusErrorBudgetAlarmProps {\n /**\n * The name of the SLO. Assumed to be unique per stack.\n */\n sloName: string;\n /**\n * The percentage target for the SLO, expressed as a decimal floating point number. Must be between 0.95 and 0.995.\n */\n sloTarget: number;\n badEvents: string; // TODO: explain this once it is properly understood!\n validEvents: string; // TODO: explain this once it is properly understood!\n}\n\ninterface BurnRate {\n speed: \"Fast\" | \"Medium\" | \"Slow\";\n burnRate: number;\n}\n\ninterface CloudWatchBurnRateMonitoring extends BurnRate {\n longPeriod: Duration;\n shortPeriod: Duration;\n}\n\ninterface PrometheusBurnRateMonitoring extends BurnRate {\n longWindow: string;\n shortWindow: string;\n}\n\ninterface MonitorBurnRateForPeriodProps {\n alarmName: string;\n burnRateSpeed: BurnRate;\n period: Duration;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n}\n\ninterface CompositeBurnRateAlarmProps {\n sloName: string;\n burnRateMonitoring: CloudWatchBurnRateMonitoring;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n snsTopic: ITopic;\n suppressorAlarm?: CompositeBurnRateAlarm;\n}\n\n// If we use a single shared Prometheus instance in the Deploy Tools account, this construct would need to be deployed\n// into that account only.\nexport class GuPrometheusErrorBudgetAlarmExperimental extends CfnRuleGroupsNamespace {\n constructor(scope: GuStack, props: PrometheusErrorBudgetAlarmProps) {\n const lowestTargetAllowed = 0.95;\n const highestTargetAllowed = 0.9995;\n if (!(props.sloTarget >= lowestTargetAllowed && props.sloTarget <= highestTargetAllowed)) {\n throw new Error(\n `ErrorBudgetAlarm only works with SLO targets between ${lowestTargetAllowed} and ${highestTargetAllowed}`\n );\n }\n\n const errorBudget = 1 - props.sloTarget;\n\n const fastBurnRate: PrometheusBurnRateMonitoring = {\n speed: \"Fast\",\n burnRate: 14.4,\n longWindow: \"1h\",\n shortWindow: \"5m\",\n };\n\n const mediumBurnRate: PrometheusBurnRateMonitoring = {\n speed: \"Medium\",\n burnRate: 6,\n longWindow: \"6h\",\n shortWindow: \"30m\",\n };\n\n const slowBurnRate: PrometheusBurnRateMonitoring = {\n speed: \"Slow\",\n burnRate: 3,\n longWindow: \"1d\",\n shortWindow: \"2h\",\n };\n\n const recordingRulePrefix = `sli_for_${props.sloName}`;\n\n const fastBurnShortWindowRule = `${recordingRulePrefix}_${fastBurnRate.shortWindow}`;\n const fastBurnLongWindowRule = `${recordingRulePrefix}_${fastBurnRate.longWindow}`;\n const mediumBurnShortWindowRule = `${recordingRulePrefix}_${mediumBurnRate.shortWindow}`;\n const mediumBurnLongWindowRule = `${recordingRulePrefix}_${mediumBurnRate.longWindow}`;\n const slowBurnShortWindowRule = `${recordingRulePrefix}_${slowBurnRate.shortWindow}`;\n const slowBurnLongWindowRule = `${recordingRulePrefix}_${slowBurnRate.longWindow}`;\n\n const fastBurnThreshold: number = errorBudget * fastBurnRate.burnRate;\n const mediumBurnThreshold: number = errorBudget * mediumBurnRate.burnRate;\n const slowBurnThreshold: number = errorBudget * slowBurnRate.burnRate;\n\n // Currently we assume that the job label refers to a unique app. This works fine with our simple test data but will not scale out properly.\n // Perhaps we will need to sum by (${props.app}, ${scope.stack}, ${scope.stage}) in order to scale up?\n const prometheusRules = `\n groups:\n - name: multiwindow_recording_rules\n rules:\n - record: ${fastBurnShortWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${fastBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${fastBurnRate.shortWindow}]))\n - record: ${mediumBurnShortWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${mediumBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${mediumBurnRate.shortWindow}]))\n - record: ${fastBurnLongWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${fastBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${fastBurnRate.longWindow}]))\n - record: ${slowBurnShortWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${slowBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${slowBurnRate.shortWindow}]))\n - record: ${mediumBurnLongWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${mediumBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${mediumBurnRate.longWindow}]))\n - record: ${slowBurnLongWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${slowBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${slowBurnRate.longWindow}]))\n - name: multiwindow_alerts\n rules:\n - alert: FastBurnAlarm\n expr: (${fastBurnShortWindowRule} > ${fastBurnThreshold}) and (${fastBurnLongWindowRule} > ${fastBurnThreshold})\n - alert: MediumBurnAlarm\n expr: (${mediumBurnShortWindowRule} > ${mediumBurnThreshold}) and (${mediumBurnLongWindowRule} > ${mediumBurnThreshold})\n - alert: SlowBurnAlarm\n expr: (${slowBurnShortWindowRule} > ${slowBurnThreshold}) and (${slowBurnLongWindowRule} > ${slowBurnThreshold})\n `;\n\n // TODO: figure out how to do alert silencing for lower priority alerts - presumably via a separate config file?\n\n super(scope, props.sloName, {\n // TODO: it would probably be better to get this ARN from parameter store\n workspace: `arn:aws:aps:${scope.region}:${scope.account}:workspace/ws-e4fe0f0b-2372-4309-901b-9aae06e4d9ba`,\n // N.B. there is an AWS quota limit for unique namespaces. For this simple test we are using a namespace for a\n // single SLO. This model will not scale. If AWS allow us to increase the namespace limit significantly we could\n // potentially use a namespace per service. However, it might be better to model this as one namespace per team.\n // In either case we'd need to generate the data section only (per SLO) and then combine the list of rules under a\n // single namespace.\n name: \"test-cdk-rules\",\n data: prometheusRules,\n });\n }\n}\n\n/**\n * Use this construct to create alarms that will notify you when you are at risk of missing your SLO targets.\n *\n * These alarms follow the recommendations in the [SRE Workbook](https://sre.google/workbook/alerting-on-slos/) to\n * implement Multiwindow, Multi-Burn-Rate Alerts.\n *\n * The CloudWatch implementation of this strategy creates nine separate CloudWatch alarms. The main alarms that users\n * need to care about are the three composite alarms. These track different burn rates of error budget consumption:\n * fast (e.g. complete outages), medium (e.g. moderate error percentage over hours, rather than minutes) and slow (e.g.\n * a small percentage of errors sustained over a much longer period). Multiple burn rates are used to ensure that\n * we are able to pick up all scenarios which could threaten the SLO, without interrupting developers with an\n * unnecessary level of urgency.\n *\n * Each composite alarm creates two child alarms; these are used to support multiple windows (or periods in CloudWatch\n * terminology). A composite alarm is configured to send notifications if both of its child alarms are in an alarm state.\n * The child alarms themselves have no actions/notifications configured and users should not need to interact with them\n * directly. Multiple windows are helpful for accuracy and reset time. The long window helps us to avoid sending an\n * alert in scenarios where no intervention is required, for example a very brief spike in errors that is resolved\n * automatically (e.g. when AWS replaces a 'bad' EC2 instance). The short window helps to ensure that the alert is reset\n * (or moved back into OK state, in CloudWatch terminology) promptly once a problem has been resolved.\n *\n * The composite alarms have an awareness of priority, meaning that low priority alarm notifications are suppressed if\n * a higher priority alarm is already firing. That is, if you receive an alert about fast error budget consumption, you\n * should not receive an alert from the medium or slow versions of the alarm.\n *\n * For how to instantiate an ErrorBudgetAlarm check the unit-test in error-budget-alarm.test.ts,\n * and for a description of the props that need to be passed see [[ErrorBudgetAlarmProps]]\n */\nexport class GuErrorBudgetAlarmExperimental extends Construct {\n constructor(scope: GuStack, props: CloudWatchErrorBudgetAlarmProps) {\n const lowestTargetAllowed = 0.95;\n const highestTargetAllowed = 0.9995;\n if (!(props.sloTarget >= lowestTargetAllowed && props.sloTarget <= highestTargetAllowed)) {\n throw new Error(\n `ErrorBudgetAlarm only works with SLO targets between ${lowestTargetAllowed} and ${highestTargetAllowed}`\n );\n }\n\n super(scope, props.sloName); // The assumption here is that `sloName` will be unique (per stack)\n\n const errorBudget = 1 - props.sloTarget;\n\n const snsTopic: ITopic = Topic.fromTopicArn(\n scope,\n `SnsSloAlarmsFor${props.sloName}`,\n `arn:aws:sns:${scope.region}:${scope.account}:${props.snsTopicNameForAlerts}`\n );\n\n const fastBurnRate: CloudWatchBurnRateMonitoring = {\n speed: \"Fast\",\n burnRate: 14.4,\n longPeriod: Duration.hours(1),\n shortPeriod: Duration.minutes(5),\n };\n\n const fastBurnAlarm = new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: fastBurnRate,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n });\n\n const mediumBurnRateMonitoring: CloudWatchBurnRateMonitoring = {\n speed: \"Medium\",\n burnRate: 6,\n longPeriod: Duration.hours(6),\n shortPeriod: Duration.minutes(30),\n };\n\n const mediumBurnRateAlarm = new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: mediumBurnRateMonitoring,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n suppressorAlarm: fastBurnAlarm,\n });\n\n const slowBurnRate: CloudWatchBurnRateMonitoring = {\n speed: \"Slow\",\n burnRate: 3,\n longPeriod: Duration.days(1),\n shortPeriod: Duration.hours(2),\n };\n\n new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: slowBurnRate,\n errorBudget,\n // TODO - use a different topic for tickets or figure out how to route notifications vs tickets using a single topic\n snsTopic,\n validEvents: props.validEvents,\n suppressorAlarm: mediumBurnRateAlarm,\n });\n }\n}\n\nclass MonitorBurnRateForPeriod extends Alarm {\n constructor(scope: GuStack, props: MonitorBurnRateForPeriodProps) {\n const undesirableErrorBudgetConsumption = props.errorBudget * props.burnRateSpeed.burnRate;\n super(scope, props.alarmName, {\n alarmName: props.alarmName,\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.period,\n }),\n evaluationPeriods: 1,\n threshold: undesirableErrorBudgetConsumption,\n comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,\n treatMissingData: TreatMissingData.NOT_BREACHING,\n });\n }\n}\n\nclass CompositeBurnRateAlarm extends CompositeAlarm {\n constructor(scope: GuStack, props: CompositeBurnRateAlarmProps) {\n const alarmName = `${props.sloName}${props.burnRateMonitoring.speed}BurnCompositeAlarm`;\n const sharedIndividualAlarmProps = {\n badEvents: props.badEvents,\n burnRateSpeed: {\n speed: props.burnRateMonitoring.speed,\n burnRate: props.burnRateMonitoring.burnRate,\n },\n errorBudget: props.errorBudget,\n validEvents: props.validEvents,\n };\n super(scope, alarmName, {\n alarmRule: AlarmRule.allOf(\n AlarmRule.fromAlarm(\n new MonitorBurnRateForPeriod(scope, {\n ...sharedIndividualAlarmProps,\n alarmName: `ChildAlarmLongPeriod${alarmName}`,\n period: props.burnRateMonitoring.longPeriod,\n }),\n AlarmState.ALARM\n ),\n AlarmRule.fromAlarm(\n new MonitorBurnRateForPeriod(scope, {\n ...sharedIndividualAlarmProps,\n alarmName: `ChildAlarmShortPeriod${alarmName}`,\n period: props.burnRateMonitoring.shortPeriod,\n }),\n AlarmState.ALARM\n )\n ),\n });\n this.addAlarmAction(new SnsAction(props.snsTopic));\n if (props.suppressorAlarm) {\n const cfnAlarm = this.node.defaultChild as CfnCompositeAlarm;\n cfnAlarm.actionsSuppressor = props.suppressorAlarm.alarmArn;\n cfnAlarm.actionsSuppressorWaitPeriod = 120;\n cfnAlarm.actionsSuppressorExtensionPeriod = props.burnRateMonitoring.shortPeriod.toSeconds();\n }\n }\n}\n"]}
|
|
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 { CfnRuleGroupsNamespace } from \"aws-cdk-lib/aws-aps\";\nimport type { CfnCompositeAlarm, IMetric } from \"aws-cdk-lib/aws-cloudwatch\";\nimport {\n Alarm,\n AlarmRule,\n 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 { Construct } from \"constructs\";\nimport type { GuStack } from \"../../constructs/core\";\n\nexport interface CloudWatchErrorBudgetAlarmProps {\n /**\n * The name of the SLO. Assumed to be unique per stack.\n */\n sloName: string;\n /**\n * The percentange target for the SLO, expressed as a decimal floating point number. Must be between 0.95 and 0.995.\n */\n sloTarget: number;\n /**\n * The metric representing the bad events. Can be any IMetric type, like Metric or MathExpression.\n */\n badEvents: IMetric;\n /**\n * The metric representing the events to be considered. Can be any IMetric type, like Metric or MathExpression.\n */\n validEvents: IMetric;\n /**\n * The sns topic name (not the full arn) used by the alarm to send email alerts.\n */\n snsTopicNameForAlerts: string;\n}\n\nexport interface PrometheusErrorBudgetAlarmProps {\n /**\n * The name of the SLO. Assumed to be unique per stack.\n */\n sloName: string;\n /**\n * The percentage target for the SLO, expressed as a decimal floating point number. Must be between 0.95 and 0.995.\n */\n sloTarget: number;\n badEvents: string; // TODO: explain this once it is properly understood!\n validEvents: string; // TODO: explain this once it is properly understood!\n}\n\ninterface BurnRate {\n speed: \"Fast\" | \"Medium\" | \"Slow\";\n burnRate: number;\n}\n\ninterface CloudWatchBurnRateMonitoring extends BurnRate {\n longPeriod: Duration;\n shortPeriod: Duration;\n}\n\ninterface PrometheusBurnRateMonitoring extends BurnRate {\n longWindow: string;\n shortWindow: string;\n}\n\ninterface MonitorBurnRateForPeriodProps {\n alarmName: string;\n burnRateSpeed: BurnRate;\n period: Duration;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n}\n\ninterface CompositeBurnRateAlarmProps {\n sloName: string;\n burnRateMonitoring: CloudWatchBurnRateMonitoring;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n snsTopic: ITopic;\n suppressorAlarm?: CompositeBurnRateAlarm;\n}\n\n// If we use a single shared Prometheus instance in the Deploy Tools account, this construct would need to be deployed\n// into that account only.\nexport class GuPrometheusErrorBudgetAlarmExperimental extends CfnRuleGroupsNamespace {\n constructor(scope: GuStack, props: PrometheusErrorBudgetAlarmProps) {\n const lowestTargetAllowed = 0.95;\n const highestTargetAllowed = 0.9995;\n if (!(props.sloTarget >= lowestTargetAllowed && props.sloTarget <= highestTargetAllowed)) {\n throw new Error(\n `ErrorBudgetAlarm only works with SLO targets between ${lowestTargetAllowed} and ${highestTargetAllowed}`\n );\n }\n\n const errorBudget = 1 - props.sloTarget;\n\n const fastBurnRate: PrometheusBurnRateMonitoring = {\n speed: \"Fast\",\n burnRate: 14.4,\n longWindow: \"1h\",\n shortWindow: \"5m\",\n };\n\n const mediumBurnRate: PrometheusBurnRateMonitoring = {\n speed: \"Medium\",\n burnRate: 6,\n longWindow: \"6h\",\n shortWindow: \"30m\",\n };\n\n const slowBurnRate: PrometheusBurnRateMonitoring = {\n speed: \"Slow\",\n burnRate: 3,\n longWindow: \"1d\",\n shortWindow: \"2h\",\n };\n\n const recordingRulePrefix = `sli_for_${props.sloName}`;\n\n const fastBurnShortWindowRule = `${recordingRulePrefix}_${fastBurnRate.shortWindow}`;\n const fastBurnLongWindowRule = `${recordingRulePrefix}_${fastBurnRate.longWindow}`;\n const mediumBurnShortWindowRule = `${recordingRulePrefix}_${mediumBurnRate.shortWindow}`;\n const mediumBurnLongWindowRule = `${recordingRulePrefix}_${mediumBurnRate.longWindow}`;\n const slowBurnShortWindowRule = `${recordingRulePrefix}_${slowBurnRate.shortWindow}`;\n const slowBurnLongWindowRule = `${recordingRulePrefix}_${slowBurnRate.longWindow}`;\n\n const fastBurnThreshold: number = errorBudget * fastBurnRate.burnRate;\n const mediumBurnThreshold: number = errorBudget * mediumBurnRate.burnRate;\n const slowBurnThreshold: number = errorBudget * slowBurnRate.burnRate;\n\n // Currently we assume that the job label refers to a unique app. This works fine with our simple test data but will not scale out properly.\n // Perhaps we will need to sum by (${props.app}, ${scope.stack}, ${scope.stage}) in order to scale up?\n const prometheusRules = `groups:\n - name: multiwindow_recording_rules\n rules:\n - record: ${fastBurnShortWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${fastBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${fastBurnRate.shortWindow}]))\n - record: ${mediumBurnShortWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${mediumBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${mediumBurnRate.shortWindow}]))\n - record: ${fastBurnLongWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${fastBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${fastBurnRate.longWindow}]))\n - record: ${slowBurnShortWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${slowBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${slowBurnRate.shortWindow}]))\n - record: ${mediumBurnLongWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${mediumBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${mediumBurnRate.longWindow}]))\n - record: ${slowBurnLongWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${slowBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${slowBurnRate.longWindow}]))\n - name: multiwindow_alerts\n rules:\n - alert: FastBurnAlarm\n expr: (${fastBurnShortWindowRule} > ${fastBurnThreshold}) and (${fastBurnLongWindowRule} > ${fastBurnThreshold})\n - alert: MediumBurnAlarm\n expr: (${mediumBurnShortWindowRule} > ${mediumBurnThreshold}) and (${mediumBurnLongWindowRule} > ${mediumBurnThreshold})\n - alert: SlowBurnAlarm\n expr: (${slowBurnShortWindowRule} > ${slowBurnThreshold}) and (${slowBurnLongWindowRule} > ${slowBurnThreshold})\n `;\n\n // TODO: figure out how to do alert silencing for lower priority alerts - presumably via a separate config file?\n\n super(scope, props.sloName, {\n // TODO: it would probably be better to get this ARN from parameter store\n workspace: `arn:aws:aps:${scope.region}:${scope.account}:workspace/ws-e4fe0f0b-2372-4309-901b-9aae06e4d9ba`,\n // N.B. there is an AWS quota limit for unique namespaces. For this simple test we are using a namespace for a\n // single SLO. This model will not scale. If AWS allow us to increase the namespace limit significantly we could\n // potentially use a namespace per service. However, it might be better to model this as one namespace per team.\n // In either case we'd need to generate the data section only (per SLO) and then combine the list of rules under a\n // single namespace.\n name: \"test-cdk-rules\",\n data: prometheusRules,\n });\n }\n}\n\n/**\n * Use this construct to create alarms that will notify you when you are at risk of missing your SLO targets.\n *\n * These alarms follow the recommendations in the [SRE Workbook](https://sre.google/workbook/alerting-on-slos/) to\n * implement Multiwindow, Multi-Burn-Rate Alerts.\n *\n * The CloudWatch implementation of this strategy creates nine separate CloudWatch alarms. The main alarms that users\n * need to care about are the three composite alarms. These track different burn rates of error budget consumption:\n * fast (e.g. complete outages), medium (e.g. moderate error percentage over hours, rather than minutes) and slow (e.g.\n * a small percentage of errors sustained over a much longer period). Multiple burn rates are used to ensure that\n * we are able to pick up all scenarios which could threaten the SLO, without interrupting developers with an\n * unnecessary level of urgency.\n *\n * Each composite alarm creates two child alarms; these are used to support multiple windows (or periods in CloudWatch\n * terminology). A composite alarm is configured to send notifications if both of its child alarms are in an alarm state.\n * The child alarms themselves have no actions/notifications configured and users should not need to interact with them\n * directly. Multiple windows are helpful for accuracy and reset time. The long window helps us to avoid sending an\n * alert in scenarios where no intervention is required, for example a very brief spike in errors that is resolved\n * automatically (e.g. when AWS replaces a 'bad' EC2 instance). The short window helps to ensure that the alert is reset\n * (or moved back into OK state, in CloudWatch terminology) promptly once a problem has been resolved.\n *\n * The composite alarms have an awareness of priority, meaning that low priority alarm notifications are suppressed if\n * a higher priority alarm is already firing. That is, if you receive an alert about fast error budget consumption, you\n * should not receive an alert from the medium or slow versions of the alarm.\n *\n * For how to instantiate an ErrorBudgetAlarm check the unit-test in error-budget-alarm.test.ts,\n * and for a description of the props that need to be passed see [[ErrorBudgetAlarmProps]]\n */\nexport class GuErrorBudgetAlarmExperimental extends Construct {\n constructor(scope: GuStack, props: CloudWatchErrorBudgetAlarmProps) {\n const lowestTargetAllowed = 0.95;\n const highestTargetAllowed = 0.9995;\n if (!(props.sloTarget >= lowestTargetAllowed && props.sloTarget <= highestTargetAllowed)) {\n throw new Error(\n `ErrorBudgetAlarm only works with SLO targets between ${lowestTargetAllowed} and ${highestTargetAllowed}`\n );\n }\n\n super(scope, props.sloName); // The assumption here is that `sloName` will be unique (per stack)\n\n const errorBudget = 1 - props.sloTarget;\n\n const snsTopic: ITopic = Topic.fromTopicArn(\n scope,\n `SnsSloAlarmsFor${props.sloName}`,\n `arn:aws:sns:${scope.region}:${scope.account}:${props.snsTopicNameForAlerts}`\n );\n\n const fastBurnRate: CloudWatchBurnRateMonitoring = {\n speed: \"Fast\",\n burnRate: 14.4,\n longPeriod: Duration.hours(1),\n shortPeriod: Duration.minutes(5),\n };\n\n const fastBurnAlarm = new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: fastBurnRate,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n });\n\n const mediumBurnRateMonitoring: CloudWatchBurnRateMonitoring = {\n speed: \"Medium\",\n burnRate: 6,\n longPeriod: Duration.hours(6),\n shortPeriod: Duration.minutes(30),\n };\n\n const mediumBurnRateAlarm = new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: mediumBurnRateMonitoring,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n suppressorAlarm: fastBurnAlarm,\n });\n\n const slowBurnRate: CloudWatchBurnRateMonitoring = {\n speed: \"Slow\",\n burnRate: 3,\n longPeriod: Duration.days(1),\n shortPeriod: Duration.hours(2),\n };\n\n new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: slowBurnRate,\n errorBudget,\n // TODO - use a different topic for tickets or figure out how to route notifications vs tickets using a single topic\n snsTopic,\n validEvents: props.validEvents,\n suppressorAlarm: mediumBurnRateAlarm,\n });\n }\n}\n\nclass MonitorBurnRateForPeriod extends Alarm {\n constructor(scope: GuStack, props: MonitorBurnRateForPeriodProps) {\n const undesirableErrorBudgetConsumption = props.errorBudget * props.burnRateSpeed.burnRate;\n super(scope, props.alarmName, {\n alarmName: props.alarmName,\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.period,\n }),\n evaluationPeriods: 1,\n threshold: undesirableErrorBudgetConsumption,\n comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,\n treatMissingData: TreatMissingData.NOT_BREACHING,\n });\n }\n}\n\nclass CompositeBurnRateAlarm extends CompositeAlarm {\n constructor(scope: GuStack, props: CompositeBurnRateAlarmProps) {\n const alarmName = `${props.sloName}${props.burnRateMonitoring.speed}BurnCompositeAlarm`;\n const sharedIndividualAlarmProps = {\n badEvents: props.badEvents,\n burnRateSpeed: {\n speed: props.burnRateMonitoring.speed,\n burnRate: props.burnRateMonitoring.burnRate,\n },\n errorBudget: props.errorBudget,\n validEvents: props.validEvents,\n };\n super(scope, alarmName, {\n alarmRule: AlarmRule.allOf(\n AlarmRule.fromAlarm(\n new MonitorBurnRateForPeriod(scope, {\n ...sharedIndividualAlarmProps,\n alarmName: `ChildAlarmLongPeriod${alarmName}`,\n period: props.burnRateMonitoring.longPeriod,\n }),\n AlarmState.ALARM\n ),\n AlarmRule.fromAlarm(\n new MonitorBurnRateForPeriod(scope, {\n ...sharedIndividualAlarmProps,\n alarmName: `ChildAlarmShortPeriod${alarmName}`,\n period: props.burnRateMonitoring.shortPeriod,\n }),\n AlarmState.ALARM\n )\n ),\n });\n this.addAlarmAction(new SnsAction(props.snsTopic));\n if (props.suppressorAlarm) {\n const cfnAlarm = this.node.defaultChild as CfnCompositeAlarm;\n cfnAlarm.actionsSuppressor = props.suppressorAlarm.alarmArn;\n cfnAlarm.actionsSuppressorWaitPeriod = 120;\n cfnAlarm.actionsSuppressorExtensionPeriod = props.burnRateMonitoring.shortPeriod.toSeconds();\n }\n }\n}\n"]}
|