@guardian/cdk 48.6.0-beta.2 → 49.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.
Files changed (71) hide show
  1. package/README.md +1 -1
  2. package/lib/bin/commands/new-project/index.d.ts +1 -1
  3. package/lib/bin/commands/new-project/index.d.ts.map +1 -1
  4. package/lib/constants/metadata-keys.d.ts +0 -2
  5. package/lib/constants/metadata-keys.d.ts.map +1 -1
  6. package/lib/constants/metadata-keys.js +0 -2
  7. package/lib/constants/metadata-keys.js.map +1 -1
  8. package/lib/constructs/acm/certificate.d.ts +1 -1
  9. package/lib/constructs/acm/certificate.d.ts.map +1 -1
  10. package/lib/constructs/autoscaling/user-data.d.ts +1 -1
  11. package/lib/constructs/autoscaling/user-data.d.ts.map +1 -1
  12. package/lib/constructs/autoscaling/user-data.js +8 -8
  13. package/lib/constructs/autoscaling/user-data.js.map +1 -1
  14. package/lib/constructs/core/parameters/base.d.ts +2 -2
  15. package/lib/constructs/core/parameters/base.d.ts.map +1 -1
  16. package/lib/constructs/core/stack.js +27 -27
  17. package/lib/constructs/core/stack.js.map +1 -1
  18. package/lib/constructs/ec2/vpc.d.ts +1 -1
  19. package/lib/constructs/ec2/vpc.d.ts.map +1 -1
  20. package/lib/constructs/ecs/ecs-task.d.ts +4 -4
  21. package/lib/constructs/ecs/ecs-task.d.ts.map +1 -1
  22. package/lib/constructs/iam/policies/base-policy.d.ts +3 -3
  23. package/lib/constructs/iam/policies/base-policy.d.ts.map +1 -1
  24. package/lib/constructs/iam/roles/instance-role.d.ts +1 -1
  25. package/lib/constructs/iam/roles/instance-role.d.ts.map +1 -1
  26. package/lib/constructs/iam/roles/roles.d.ts +1 -1
  27. package/lib/constructs/iam/roles/roles.d.ts.map +1 -1
  28. package/lib/constructs/kinesis/kinesis-stream.d.ts +1 -1
  29. package/lib/constructs/kinesis/kinesis-stream.d.ts.map +1 -1
  30. package/lib/constructs/vpc/vpc.d.ts +1 -1
  31. package/lib/constructs/vpc/vpc.d.ts.map +1 -1
  32. package/lib/constructs/vpc/vpc.js +1 -1
  33. package/lib/constructs/vpc/vpc.js.map +1 -1
  34. package/lib/experimental/constructs/error-budget-alarm.d.ts +2 -18
  35. package/lib/experimental/constructs/error-budget-alarm.d.ts.map +1 -1
  36. package/lib/experimental/constructs/error-budget-alarm.js +1 -81
  37. package/lib/experimental/constructs/error-budget-alarm.js.map +1 -1
  38. package/lib/experimental/patterns/sns-lambda.d.ts.map +1 -1
  39. package/lib/experimental/patterns/sns-lambda.js +1 -2
  40. package/lib/experimental/patterns/sns-lambda.js.map +1 -1
  41. package/lib/experimental/riff-raff-yaml-file/index.js +48 -48
  42. package/lib/experimental/riff-raff-yaml-file/index.js.map +1 -1
  43. package/lib/experimental/riff-raff-yaml-file/types.d.ts +7 -7
  44. package/lib/experimental/riff-raff-yaml-file/types.d.ts.map +1 -1
  45. package/lib/patterns/api-multiple-lambdas.d.ts +1 -1
  46. package/lib/patterns/api-multiple-lambdas.d.ts.map +1 -1
  47. package/lib/patterns/ec2-app/base.d.ts +0 -21
  48. package/lib/patterns/ec2-app/base.d.ts.map +1 -1
  49. package/lib/patterns/ec2-app/base.js +0 -33
  50. package/lib/patterns/ec2-app/base.js.map +1 -1
  51. package/lib/patterns/ec2-app/framework.d.ts +1 -1
  52. package/lib/patterns/ec2-app/framework.d.ts.map +1 -1
  53. package/lib/types/access.d.ts +1 -1
  54. package/lib/types/access.d.ts.map +1 -1
  55. package/lib/types/cli.d.ts +1 -1
  56. package/lib/types/cli.d.ts.map +1 -1
  57. package/lib/utils/lambda/event-source.d.ts +4 -4
  58. package/lib/utils/lambda/event-source.d.ts.map +1 -1
  59. package/lib/utils/lambda/event-source.js +4 -4
  60. package/lib/utils/lambda/event-source.js.map +1 -1
  61. package/lib/utils/mixin/types.d.ts +1 -1
  62. package/lib/utils/mixin/types.d.ts.map +1 -1
  63. package/package.json +18 -18
  64. package/lib/constructs/sns/index.d.ts +0 -2
  65. package/lib/constructs/sns/index.d.ts.map +0 -1
  66. package/lib/constructs/sns/index.js +0 -18
  67. package/lib/constructs/sns/index.js.map +0 -1
  68. package/lib/constructs/sns/sns-topic.d.ts +0 -15
  69. package/lib/constructs/sns/sns-topic.d.ts.map +0 -1
  70. package/lib/constructs/sns/sns-topic.js +0 -17
  71. package/lib/constructs/sns/sns-topic.js.map +0 -1
@@ -1,91 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GuErrorBudgetAlarmExperimental = exports.GuPrometheusErrorBudgetAlarmExperimental = void 0;
3
+ exports.GuErrorBudgetAlarmExperimental = void 0;
4
4
  const aws_cdk_lib_1 = require("aws-cdk-lib");
5
- const aws_aps_1 = require("aws-cdk-lib/aws-aps");
6
5
  const aws_cloudwatch_1 = require("aws-cdk-lib/aws-cloudwatch");
7
6
  const aws_cloudwatch_actions_1 = require("aws-cdk-lib/aws-cloudwatch-actions");
8
7
  const aws_sns_1 = require("aws-cdk-lib/aws-sns");
9
8
  const constructs_1 = require("constructs");
10
- // If we use a single shared Prometheus instance in the Deploy Tools account, this construct would need to be deployed
11
- // into that account only.
12
- class GuPrometheusErrorBudgetAlarmExperimental extends aws_aps_1.CfnRuleGroupsNamespace {
13
- constructor(scope, props) {
14
- const lowestTargetAllowed = 0.95;
15
- const highestTargetAllowed = 0.9995;
16
- if (!(props.sloTarget >= lowestTargetAllowed && props.sloTarget <= highestTargetAllowed)) {
17
- throw new Error(`ErrorBudgetAlarm only works with SLO targets between ${lowestTargetAllowed} and ${highestTargetAllowed}`);
18
- }
19
- const errorBudget = 1 - props.sloTarget;
20
- const fastBurnRate = {
21
- speed: "Fast",
22
- burnRate: 14.4,
23
- longWindow: "1h",
24
- shortWindow: "5m",
25
- };
26
- const mediumBurnRate = {
27
- speed: "Medium",
28
- burnRate: 6,
29
- longWindow: "6h",
30
- shortWindow: "30m",
31
- };
32
- const slowBurnRate = {
33
- speed: "Slow",
34
- burnRate: 3,
35
- longWindow: "1d",
36
- shortWindow: "2h",
37
- };
38
- const recordingRulePrefix = `sli_for_${props.sloName}`;
39
- const fastBurnShortWindowRule = `${recordingRulePrefix}_${fastBurnRate.shortWindow}`;
40
- const fastBurnLongWindowRule = `${recordingRulePrefix}_${fastBurnRate.longWindow}`;
41
- const mediumBurnShortWindowRule = `${recordingRulePrefix}_${mediumBurnRate.shortWindow}`;
42
- const mediumBurnLongWindowRule = `${recordingRulePrefix}_${mediumBurnRate.longWindow}`;
43
- const slowBurnShortWindowRule = `${recordingRulePrefix}_${slowBurnRate.shortWindow}`;
44
- const slowBurnLongWindowRule = `${recordingRulePrefix}_${slowBurnRate.longWindow}`;
45
- const fastBurnThreshold = errorBudget * fastBurnRate.burnRate;
46
- const mediumBurnThreshold = errorBudget * mediumBurnRate.burnRate;
47
- const slowBurnThreshold = errorBudget * slowBurnRate.burnRate;
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
- // Perhaps we will need to sum by (${props.app}, ${scope.stack}, ${scope.stage}) in order to scale up?
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
- `;
74
- // TODO: figure out how to do alert silencing for lower priority alerts - presumably via a separate config file?
75
- super(scope, props.sloName, {
76
- // TODO: it would probably be better to get this ARN from parameter store
77
- workspace: `arn:aws:aps:${scope.region}:${scope.account}:workspace/ws-e4fe0f0b-2372-4309-901b-9aae06e4d9ba`,
78
- // N.B. there is an AWS quota limit for unique namespaces. For this simple test we are using a namespace for a
79
- // single SLO. This model will not scale. If AWS allow us to increase the namespace limit significantly we could
80
- // potentially use a namespace per service. However, it might be better to model this as one namespace per team.
81
- // In either case we'd need to generate the data section only (per SLO) and then combine the list of rules under a
82
- // single namespace.
83
- name: "test-cdk-rules",
84
- data: prometheusRules,
85
- });
86
- }
87
- }
88
- exports.GuPrometheusErrorBudgetAlarmExperimental = GuPrometheusErrorBudgetAlarmExperimental;
89
9
  /**
90
10
  * Use this construct to create alarms that will notify you when you are at risk of missing your SLO targets.
91
11
  *
@@ -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 = `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 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 ErrorBudgetAlarmProps {\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\ninterface BurnRate {\n speed: \"Fast\" | \"Medium\" | \"Slow\";\n burnRate: number;\n}\n\ninterface BurnRateMonitoring extends BurnRate {\n longPeriod: Duration;\n shortPeriod: Duration;\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: BurnRateMonitoring;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n snsTopic: ITopic;\n suppressorAlarm?: CompositeBurnRateAlarm;\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: ErrorBudgetAlarmProps) {\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: BurnRateMonitoring = {\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: BurnRateMonitoring = {\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: BurnRateMonitoring = {\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 +1 @@
1
- {"version":3,"file":"sns-lambda.d.ts","sourceRoot":"","sources":["../../../src/experimental/patterns/sns-lambda.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,KAAK,EAAE,sCAAsC,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAExG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG/D;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,eAAe,EAAE,2BAA2B,CAAC;IAC1F,uBAAuB,EAAE,YAAY,GAAG,sCAAsC,CAAC;IAC/E,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,uBAAwB,SAAQ,gBAAgB;IAC3D,SAAgB,QAAQ,EAAE,MAAM,CAAC;gBAErB,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB;CAoBhE"}
1
+ {"version":3,"file":"sns-lambda.d.ts","sourceRoot":"","sources":["../../../src/experimental/patterns/sns-lambda.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,KAAK,EAAE,sCAAsC,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAExG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,eAAe,EAAE,2BAA2B,CAAC;IAC1F,uBAAuB,EAAE,YAAY,GAAG,sCAAsC,CAAC;IAC/E,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,uBAAwB,SAAQ,gBAAgB;IAC3D,SAAgB,QAAQ,EAAE,MAAM,CAAC;gBAErB,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB;CAoBhE"}
@@ -6,7 +6,6 @@ const aws_lambda_event_sources_1 = require("aws-cdk-lib/aws-lambda-event-sources
6
6
  const aws_sns_1 = require("aws-cdk-lib/aws-sns");
7
7
  const core_1 = require("../../constructs/core");
8
8
  const lambda_1 = require("../../constructs/lambda");
9
- const sns_1 = require("../../constructs/sns");
10
9
  /**
11
10
  * Pattern which creates all of the resources needed to invoke a lambda function whenever a message
12
11
  * is published onto an SNS topic.
@@ -30,7 +29,7 @@ class GuSnsLambdaExperimental extends lambda_1.GuLambdaFunction {
30
29
  const { existingSnsTopic } = props;
31
30
  this.snsTopic = existingSnsTopic
32
31
  ? aws_sns_1.Topic.fromTopicArn(scope, `${id}-SnsExistingIncomingEventsTopic`, `arn:aws:sns:${region}:${account}:${existingSnsTopic.externalTopicName}`)
33
- : core_1.AppIdentity.taggedConstruct(props, new sns_1.GuSnsTopic(scope, "SnsIncomingEventsTopic"));
32
+ : core_1.AppIdentity.taggedConstruct(props, new aws_sns_1.Topic(scope, "SnsIncomingEventsTopic"));
34
33
  this.addEventSource(new aws_lambda_event_sources_1.SnsEventSource(this.snsTopic));
35
34
  new aws_cdk_lib_1.CfnOutput(this, "TopicName", { value: this.snsTopic.topicName });
36
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sns-lambda.js","sourceRoot":"","sources":["../../../src/experimental/patterns/sns-lambda.ts"],"names":[],"mappings":";;;AAAA,6CAAwC;AACxC,mFAAsE;AAEtE,iDAA4C;AAE5C,gDAAoD;AAEpD,oDAA2D;AAE3D,8CAAkD;AA2ClD;;;;;;;;;;;;GAYG;AACH,MAAa,uBAAwB,SAAQ,yBAAgB;IAG3D,YAAY,KAAc,EAAE,EAAU,EAAE,KAAuB;QAC7D,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE;YACf,GAAG,KAAK;YACR,yBAAyB,EAAE,KAAK,CAAC,uBAAuB,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,uBAAuB;SAClH,CAAC,CAAC;QAEH,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;QAClC,MAAM,EAAE,gBAAgB,EAAE,GAAG,KAAK,CAAC;QAEnC,IAAI,CAAC,QAAQ,GAAG,gBAAgB;YAC9B,CAAC,CAAC,eAAK,CAAC,YAAY,CAChB,KAAK,EACL,GAAG,EAAE,iCAAiC,EACtC,eAAe,MAAM,IAAI,OAAO,IAAI,gBAAgB,CAAC,iBAAiB,EAAE,CACzE;YACH,CAAC,CAAC,kBAAW,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,gBAAU,CAAC,KAAK,EAAE,wBAAwB,CAAC,CAAC,CAAC;QAExF,IAAI,CAAC,cAAc,CAAC,IAAI,yCAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QACvD,IAAI,uBAAS,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IACvE,CAAC;CACF;AAvBD,0DAuBC","sourcesContent":["import { CfnOutput } from \"aws-cdk-lib\";\nimport { SnsEventSource } from \"aws-cdk-lib/aws-lambda-event-sources\";\nimport type { ITopic } from \"aws-cdk-lib/aws-sns\";\nimport { Topic } from \"aws-cdk-lib/aws-sns\";\nimport type { GuLambdaErrorPercentageMonitoringProps, NoMonitoring } from \"../../constructs/cloudwatch\";\nimport { AppIdentity } from \"../../constructs/core\";\nimport type { GuStack } from \"../../constructs/core\";\nimport { GuLambdaFunction } from \"../../constructs/lambda\";\nimport type { GuFunctionProps } from \"../../constructs/lambda\";\nimport { GuSnsTopic } from \"../../constructs/sns\";\n\n/**\n * Used to provide information about an existing SNS topic to the [[`GuSnsLambda`]] pattern.\n *\n * Specify an `externalTopicName` to link the lambda to an SNS topic owned by a different stack\n * (or created outside of version control).\n */\nexport interface ExistingSnsTopic {\n externalTopicName: string;\n}\n\n/**\n * Configuration options for the [[`GuSnsLambda`]] pattern.\n *\n * For all lambda function configuration options, see [[`GuFunctionProps`]].\n *\n * The `existingSnsTopic` property can be used to inherit or reference an SNS topic which\n * has been created outside of `cdk`. If this property is omitted, the [[`GuSnsLambda`]] pattern\n * will create a new topic. For more details and example usage, see [[`ExistingSnsTopic`]].\n *\n * It is advisable to configure an alarm based on the lambda's error percentage.\n * To do this, add the `monitoringConfiguration` property. The required properties for this are:\n *\n * ```typescript\n * monitoringConfiguration: {\n * toleratedErrorPercentage: <sensible_error_percentage_threshold>,\n * snsTopicName: \"my-topic-for-cloudwatch-alerts\",\n * }\n * ```\n * Other alarm properties (e.g. alarm name and description) will be pre-populated with sensible defaults.\n * For a full list of optional properties, see [[`GuLambdaErrorPercentageMonitoringProps`]].\n *\n * If your team do not use CloudWatch, it's possible to opt-out with the following configuration:\n * ```typescript\n * monitoringConfiguration: { noMonitoring: true } as NoMonitoring\n * ```\n */\nexport interface GuSnsLambdaProps extends Omit<GuFunctionProps, \"errorPercentageMonitoring\"> {\n monitoringConfiguration: NoMonitoring | GuLambdaErrorPercentageMonitoringProps;\n existingSnsTopic?: ExistingSnsTopic;\n}\n\n/**\n * Pattern which creates all of the resources needed to invoke a lambda function whenever a message\n * is published onto an SNS topic.\n *\n * This pattern will create a new SNS topic by default. If you are migrating a stack from CloudFormation,\n * you will need to opt-out of this behaviour. For information on overriding the default behaviour,\n * see [[`GuSnsLambdaProps`]].\n *\n * The SNS topic is stateful, and is accessible via `snsTopic`.\n * @see https://github.com/guardian/cdk/blob/main/docs/stateful-resources.md\n *\n * @experimental This pattern is in early development. The API is likely to change in future releases.\n */\nexport class GuSnsLambdaExperimental extends GuLambdaFunction {\n public readonly snsTopic: ITopic;\n\n constructor(scope: GuStack, id: string, props: GuSnsLambdaProps) {\n super(scope, id, {\n ...props,\n errorPercentageMonitoring: props.monitoringConfiguration.noMonitoring ? undefined : props.monitoringConfiguration,\n });\n\n const { account, region } = scope;\n const { existingSnsTopic } = props;\n\n this.snsTopic = existingSnsTopic\n ? Topic.fromTopicArn(\n scope,\n `${id}-SnsExistingIncomingEventsTopic`,\n `arn:aws:sns:${region}:${account}:${existingSnsTopic.externalTopicName}`\n )\n : AppIdentity.taggedConstruct(props, new GuSnsTopic(scope, \"SnsIncomingEventsTopic\"));\n\n this.addEventSource(new SnsEventSource(this.snsTopic));\n new CfnOutput(this, \"TopicName\", { value: this.snsTopic.topicName });\n }\n}\n"]}
1
+ {"version":3,"file":"sns-lambda.js","sourceRoot":"","sources":["../../../src/experimental/patterns/sns-lambda.ts"],"names":[],"mappings":";;;AAAA,6CAAwC;AACxC,mFAAsE;AAEtE,iDAA4C;AAE5C,gDAAoD;AAEpD,oDAA2D;AA4C3D;;;;;;;;;;;;GAYG;AACH,MAAa,uBAAwB,SAAQ,yBAAgB;IAG3D,YAAY,KAAc,EAAE,EAAU,EAAE,KAAuB;QAC7D,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE;YACf,GAAG,KAAK;YACR,yBAAyB,EAAE,KAAK,CAAC,uBAAuB,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,uBAAuB;SAClH,CAAC,CAAC;QAEH,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;QAClC,MAAM,EAAE,gBAAgB,EAAE,GAAG,KAAK,CAAC;QAEnC,IAAI,CAAC,QAAQ,GAAG,gBAAgB;YAC9B,CAAC,CAAC,eAAK,CAAC,YAAY,CAChB,KAAK,EACL,GAAG,EAAE,iCAAiC,EACtC,eAAe,MAAM,IAAI,OAAO,IAAI,gBAAgB,CAAC,iBAAiB,EAAE,CACzE;YACH,CAAC,CAAC,kBAAW,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,eAAK,CAAC,KAAK,EAAE,wBAAwB,CAAC,CAAC,CAAC;QAEnF,IAAI,CAAC,cAAc,CAAC,IAAI,yCAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QACvD,IAAI,uBAAS,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IACvE,CAAC;CACF;AAvBD,0DAuBC","sourcesContent":["import { CfnOutput } from \"aws-cdk-lib\";\nimport { SnsEventSource } from \"aws-cdk-lib/aws-lambda-event-sources\";\nimport type { ITopic } from \"aws-cdk-lib/aws-sns\";\nimport { Topic } from \"aws-cdk-lib/aws-sns\";\nimport type { GuLambdaErrorPercentageMonitoringProps, NoMonitoring } from \"../../constructs/cloudwatch\";\nimport { AppIdentity } from \"../../constructs/core\";\nimport type { GuStack } from \"../../constructs/core\";\nimport { GuLambdaFunction } from \"../../constructs/lambda\";\nimport type { GuFunctionProps } from \"../../constructs/lambda\";\n\n/**\n * Used to provide information about an existing SNS topic to the [[`GuSnsLambda`]] pattern.\n *\n * Specify an `externalTopicName` to link the lambda to an SNS topic owned by a different stack\n * (or created outside of version control).\n */\nexport interface ExistingSnsTopic {\n externalTopicName: string;\n}\n\n/**\n * Configuration options for the [[`GuSnsLambda`]] pattern.\n *\n * For all lambda function configuration options, see [[`GuFunctionProps`]].\n *\n * The `existingSnsTopic` property can be used to inherit or reference an SNS topic which\n * has been created outside of `cdk`. If this property is omitted, the [[`GuSnsLambda`]] pattern\n * will create a new topic. For more details and example usage, see [[`ExistingSnsTopic`]].\n *\n * It is advisable to configure an alarm based on the lambda's error percentage.\n * To do this, add the `monitoringConfiguration` property. The required properties for this are:\n *\n * ```typescript\n * monitoringConfiguration: {\n * toleratedErrorPercentage: <sensible_error_percentage_threshold>,\n * snsTopicName: \"my-topic-for-cloudwatch-alerts\",\n * }\n * ```\n * Other alarm properties (e.g. alarm name and description) will be pre-populated with sensible defaults.\n * For a full list of optional properties, see [[`GuLambdaErrorPercentageMonitoringProps`]].\n *\n * If your team do not use CloudWatch, it's possible to opt-out with the following configuration:\n * ```typescript\n * monitoringConfiguration: { noMonitoring: true } as NoMonitoring\n * ```\n */\nexport interface GuSnsLambdaProps extends Omit<GuFunctionProps, \"errorPercentageMonitoring\"> {\n monitoringConfiguration: NoMonitoring | GuLambdaErrorPercentageMonitoringProps;\n existingSnsTopic?: ExistingSnsTopic;\n}\n\n/**\n * Pattern which creates all of the resources needed to invoke a lambda function whenever a message\n * is published onto an SNS topic.\n *\n * This pattern will create a new SNS topic by default. If you are migrating a stack from CloudFormation,\n * you will need to opt-out of this behaviour. For information on overriding the default behaviour,\n * see [[`GuSnsLambdaProps`]].\n *\n * The SNS topic is stateful, and is accessible via `snsTopic`.\n * @see https://github.com/guardian/cdk/blob/main/docs/stateful-resources.md\n *\n * @experimental This pattern is in early development. The API is likely to change in future releases.\n */\nexport class GuSnsLambdaExperimental extends GuLambdaFunction {\n public readonly snsTopic: ITopic;\n\n constructor(scope: GuStack, id: string, props: GuSnsLambdaProps) {\n super(scope, id, {\n ...props,\n errorPercentageMonitoring: props.monitoringConfiguration.noMonitoring ? undefined : props.monitoringConfiguration,\n });\n\n const { account, region } = scope;\n const { existingSnsTopic } = props;\n\n this.snsTopic = existingSnsTopic\n ? Topic.fromTopicArn(\n scope,\n `${id}-SnsExistingIncomingEventsTopic`,\n `arn:aws:sns:${region}:${account}:${existingSnsTopic.externalTopicName}`\n )\n : AppIdentity.taggedConstruct(props, new Topic(scope, \"SnsIncomingEventsTopic\"));\n\n this.addEventSource(new SnsEventSource(this.snsTopic));\n new CfnOutput(this, \"TopicName\", { value: this.snsTopic.topicName });\n }\n}\n"]}
@@ -60,54 +60,6 @@ const group_by_1 = require("./group-by");
60
60
  * @see https://riffraff.gutools.co.uk/docs/magenta-lib/types
61
61
  */
62
62
  class RiffRaffYamlFileExperimental {
63
- // eslint-disable-next-line custom-rules/valid-constructors -- this needs to sit above GuStack on the cdk tree
64
- constructor(app) {
65
- this.allCdkStacks = app.node.findAll().filter((_) => _ instanceof core_1.GuStack);
66
- const allowedStages = new Set(this.allCdkStacks.map(({ stage }) => stage));
67
- this.allStageTags = Array.from(allowedStages);
68
- this.allStackTags = Array.from(new Set(this.allCdkStacks.map(({ stack }) => stack)));
69
- this.allRegions = Array.from(new Set(this.allCdkStacks.map(({ region }) => region)));
70
- this.validateStacksInApp();
71
- this.validateAllRegionsAreResolved();
72
- this.outdir = app.outdir;
73
- const deployments = new Map();
74
- const groupedStacks = (0, group_by_1.groupByClassNameStackRegionStage)(this.allCdkStacks);
75
- Object.values(groupedStacks).forEach((stackTagGroup) => {
76
- Object.values(stackTagGroup).forEach((regionGroup) => {
77
- Object.values(regionGroup).forEach((stageGroup) => {
78
- const stacks = Object.values(stageGroup).flat();
79
- if (stacks.length === 0) {
80
- throw new Error("Unable to produce a working riff-raff.yaml file; there are no stacks!");
81
- }
82
- // The items in `stacks` only differ by stage, so we can just use the first item in the list.
83
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- length of `stacks` is checked above
84
- const stack = stacks[0];
85
- const lambdas = this.getLambdas(stack);
86
- const autoscalingGroups = this.getAutoScalingGroups(stack);
87
- const artifactUploads = [
88
- lambdas.map(lambda_2.uploadLambdaArtifact),
89
- autoscalingGroups.map(autoscaling_2.uploadAutoscalingArtifact),
90
- ].flat();
91
- artifactUploads.forEach(({ name, props }) => deployments.set(name, props));
92
- const cfnDeployment = (0, cloudformation_1.cloudFormationDeployment)(stacks, artifactUploads, this.outdir);
93
- deployments.set(cfnDeployment.name, cfnDeployment.props);
94
- lambdas.forEach((lambda) => {
95
- const lambdaDeployment = (0, lambda_2.updateLambdaDeployment)(lambda, cfnDeployment);
96
- deployments.set(lambdaDeployment.name, lambdaDeployment.props);
97
- });
98
- autoscalingGroups.forEach((asg) => {
99
- const asgDeployment = (0, autoscaling_2.autoscalingDeployment)(asg, cfnDeployment);
100
- deployments.set(asgDeployment.name, asgDeployment.props);
101
- deployments.set(cfnDeployment.name, (0, cloudformation_1.addAmiParametersToCloudFormationDeployment)(cfnDeployment, asg));
102
- });
103
- });
104
- });
105
- });
106
- this.riffRaffYaml = {
107
- allowedStages,
108
- deployments,
109
- };
110
- }
111
63
  isCdkStackPresent(expectedClassName, expectedStack, expectedRegion, expectedStage) {
112
64
  const matches = this.allCdkStacks.find((cdkStack) => {
113
65
  const { constructor: { name }, stack, stage, region, } = cdkStack;
@@ -223,6 +175,54 @@ class RiffRaffYamlFileExperimental {
223
175
  getAutoScalingGroups(cdkStack) {
224
176
  return cdkStack.node.findAll().filter((_) => _ instanceof autoscaling_1.GuAutoScalingGroup);
225
177
  }
178
+ // eslint-disable-next-line custom-rules/valid-constructors -- this needs to sit above GuStack on the cdk tree
179
+ constructor(app) {
180
+ this.allCdkStacks = app.node.findAll().filter((_) => _ instanceof core_1.GuStack);
181
+ const allowedStages = new Set(this.allCdkStacks.map(({ stage }) => stage));
182
+ this.allStageTags = Array.from(allowedStages);
183
+ this.allStackTags = Array.from(new Set(this.allCdkStacks.map(({ stack }) => stack)));
184
+ this.allRegions = Array.from(new Set(this.allCdkStacks.map(({ region }) => region)));
185
+ this.validateStacksInApp();
186
+ this.validateAllRegionsAreResolved();
187
+ this.outdir = app.outdir;
188
+ const deployments = new Map();
189
+ const groupedStacks = (0, group_by_1.groupByClassNameStackRegionStage)(this.allCdkStacks);
190
+ Object.values(groupedStacks).forEach((stackTagGroup) => {
191
+ Object.values(stackTagGroup).forEach((regionGroup) => {
192
+ Object.values(regionGroup).forEach((stageGroup) => {
193
+ const stacks = Object.values(stageGroup).flat();
194
+ if (stacks.length === 0) {
195
+ throw new Error("Unable to produce a working riff-raff.yaml file; there are no stacks!");
196
+ }
197
+ // The items in `stacks` only differ by stage, so we can just use the first item in the list.
198
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- length of `stacks` is checked above
199
+ const stack = stacks[0];
200
+ const lambdas = this.getLambdas(stack);
201
+ const autoscalingGroups = this.getAutoScalingGroups(stack);
202
+ const artifactUploads = [
203
+ lambdas.map(lambda_2.uploadLambdaArtifact),
204
+ autoscalingGroups.map(autoscaling_2.uploadAutoscalingArtifact),
205
+ ].flat();
206
+ artifactUploads.forEach(({ name, props }) => deployments.set(name, props));
207
+ const cfnDeployment = (0, cloudformation_1.cloudFormationDeployment)(stacks, artifactUploads, this.outdir);
208
+ deployments.set(cfnDeployment.name, cfnDeployment.props);
209
+ lambdas.forEach((lambda) => {
210
+ const lambdaDeployment = (0, lambda_2.updateLambdaDeployment)(lambda, cfnDeployment);
211
+ deployments.set(lambdaDeployment.name, lambdaDeployment.props);
212
+ });
213
+ autoscalingGroups.forEach((asg) => {
214
+ const asgDeployment = (0, autoscaling_2.autoscalingDeployment)(asg, cfnDeployment);
215
+ deployments.set(asgDeployment.name, asgDeployment.props);
216
+ deployments.set(cfnDeployment.name, (0, cloudformation_1.addAmiParametersToCloudFormationDeployment)(cfnDeployment, asg));
217
+ });
218
+ });
219
+ });
220
+ });
221
+ this.riffRaffYaml = {
222
+ allowedStages,
223
+ deployments,
224
+ };
225
+ }
226
226
  /**
227
227
  * The `riff-raff.yaml` file as a string.
228
228
  * Useful for testing.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/experimental/riff-raff-yaml-file/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeFileSync } from \"fs\";\nimport path from \"path\";\nimport type { App } from \"aws-cdk-lib\";\nimport { Token } from \"aws-cdk-lib\";\nimport chalk from \"chalk\";\nimport { dump } from \"js-yaml\";\nimport { GuAutoScalingGroup } from \"../../constructs/autoscaling\";\nimport { GuStack } from \"../../constructs/core\";\nimport { GuLambdaFunction } from \"../../constructs/lambda\";\nimport { autoscalingDeployment, uploadAutoscalingArtifact } from \"./deployments/autoscaling\";\nimport { addAmiParametersToCloudFormationDeployment, cloudFormationDeployment } from \"./deployments/cloudformation\";\nimport { updateLambdaDeployment, uploadLambdaArtifact } from \"./deployments/lambda\";\nimport { groupByClassNameStackRegionStage } from \"./group-by\";\nimport type {\n ClassName,\n GroupedCdkStacks,\n Region,\n RiffRaffDeployment,\n RiffRaffDeploymentName,\n RiffRaffDeploymentProps,\n RiffRaffYaml,\n StackTag,\n StageTag,\n} from \"./types\";\n\n/**\n * A class that creates a `riff-raff.yaml` file.\n *\n * Rather than using this directly, prefer to use [[`GuRoot`]] instead.\n *\n * Supports:\n * - Multiple CloudFormation stacks\n * - Multiple regions\n * - Lambda applications\n * - EC2 (autoscaling) applications\n *\n * For lambda applications, 3 deployments will be defined:\n * 1. Lambda upload (`aws-lambda`, `action: [uploadLambda]`)\n * 2. CloudFormation deploy (`cloud-formation`)\n * 3. Lambda update (`aws-lambda`, `action: [updateLambda]`)\n *\n * For EC2 applications, 3 deployments will be defined:\n * 1. Artifact upload (`autoscaling`, `action: [uploadArtifacts]`)\n * 2. CloudFormation deploy (`cloud-formation`)\n * 3. Autoscaling deploy (`autoscaling`, `action: [deploy]`)\n *\n * It assumes a Riff-Raff bundle structure as follows:\n *\n * ```\n * .\n * ├── cdk.out\n * │ └── MyApplication.template.json\n * ├── my-application\n * │ └── my-application.deb\n * └── my-lambda\n * └── my-lambda.zip\n * ```\n *\n * That is, all CloudFormation templates are in a `cdk.out` directory, and there is a directory per app.\n * Application artifact(s) are in the app directory.\n *\n * NOTE: The file extension is decided by you, the above file tree is used for illustration purposes.\n *\n * NOTE: Resources will be looked up by tags (Stack, Stage, App). Ensure your CFN stack is tagged appropriately!\n *\n * @see https://riffraff.gutools.co.uk/docs/reference/riff-raff.yaml.md\n * @see https://riffraff.gutools.co.uk/docs/magenta-lib/types\n */\nexport class RiffRaffYamlFileExperimental {\n private readonly allCdkStacks: GuStack[];\n private readonly allStackTags: StackTag[];\n private readonly allStageTags: StageTag[];\n private readonly allRegions: Region[];\n\n private readonly riffRaffYaml: RiffRaffYaml;\n private readonly outdir: string;\n\n private isCdkStackPresent(\n expectedClassName: ClassName,\n expectedStack: StackTag,\n expectedRegion: Region,\n expectedStage: StageTag\n ): boolean {\n const matches = this.allCdkStacks.find((cdkStack) => {\n const {\n constructor: { name },\n stack,\n stage,\n region,\n } = cdkStack;\n return (\n name === expectedClassName && stack === expectedStack && stage === expectedStage && region === expectedRegion\n );\n });\n\n return !!matches;\n }\n\n /**\n * Check there are the appropriate number of `GuStack`s.\n * Expect to find an instance for each combination of `GuStack`, `stack`, `region`, and `stage`.\n *\n * If not valid, a message is logged describing what is missing to aid debugging.\n *\n * Given the following:\n *\n * ```ts\n * const app = new App();\n *\n * class MyApplicationStack extends GuStack { }\n *\n * new MyApplicationStack(app, \"App-CODE-deploy\", {\n * env: {\n * region: \"eu-west-1\",\n * },\n * stack: \"deploy\",\n * stage: \"CODE\"\n * });\n *\n * new MyApplicationStack(app, \"App-PROD-media-service\", {\n * env: {\n * region: \"eu-west-1\",\n * },\n * stack: \"media-service\",\n * stage: \"PROD\",\n * });\n *\n * new MyApplicationStack(app, \"App-PROD-deploy\", {\n * env: {\n * region: \"eu-west-1\",\n * },\n * stack: \"deploy\",\n * stage: \"PROD\"\n * });\n * ```\n *\n * This will log a message like this, where ❌ denotes something missing,\n * specifically there is no `CODE` template for `media-service`.\n *\n * ```log\n * Unable to produce a working riff-raff.yaml file; missing 1 definitions (details below)\n *\n * For the class: MyApplicationStack\n *\n * ┌───────────────┬────────────────────────────┐\n * │ (index) │ eu-west-1 │\n * ├───────────────┼────────────────────────────┤\n * │ deploy │ { CODE: '✅', PROD: '✅' } │\n * │ media-service │ { CODE: '❌', PROD: '✅' } │\n * └───────────────┴────────────────────────────┘\n * ```\n *\n * @private\n */\n private validateStacksInApp(): void {\n type Found = \"✅\";\n type NotFound = \"❌\";\n type AppValidation = Record<ClassName, Record<StackTag, Record<Region, Record<StageTag, Found | NotFound>>>>;\n\n const { allCdkStacks, allStackTags, allStageTags, allRegions } = this;\n\n const checks: AppValidation = allCdkStacks.reduce((accClassName, { constructor: { name } }) => {\n return {\n ...accClassName,\n [name]: allStackTags.reduce((accStackTag, stackTag) => {\n return {\n ...accStackTag,\n [stackTag]: allRegions.reduce((accRegion, region) => {\n return {\n ...accRegion,\n [region]: allStageTags.reduce((accStageTag, stageTag) => {\n return {\n ...accStageTag,\n [stageTag]: this.isCdkStackPresent(name, stackTag, region, stageTag) ? \"✅\" : \"❌\",\n };\n }, {}),\n };\n }, {}),\n };\n }, {}),\n };\n }, {});\n\n const missingDefinitions = Object.values(checks).flatMap((groupedByStackTag) => {\n return Object.values(groupedByStackTag).flatMap((groupedByRegion) => {\n return Object.values(groupedByRegion).flatMap((groupedByStage) => {\n return Object.values(groupedByStage).filter((_) => _ === \"❌\");\n });\n });\n });\n\n if (missingDefinitions.length > 0) {\n const message = `Unable to produce a working riff-raff.yaml file; missing ${missingDefinitions.length} definitions`;\n\n console.log(`${message} (details below)`);\n Object.entries(checks).forEach(([className, detail]) => {\n console.log(`For the class: ${chalk.yellow(className)}`);\n console.table(detail);\n });\n\n throw new Error(message);\n }\n }\n\n private validateAllRegionsAreResolved(): void {\n const unresolved = this.allRegions.filter((region) => Token.isUnresolved(region));\n\n if (unresolved.length !== 0) {\n throw new Error(`Unable to produce a working riff-raff.yaml file; all stacks must have an explicit region set`);\n }\n }\n\n private getLambdas(cdkStack: GuStack): GuLambdaFunction[] {\n return cdkStack.node.findAll().filter((_) => _ instanceof GuLambdaFunction) as GuLambdaFunction[];\n }\n\n private getAutoScalingGroups(cdkStack: GuStack): GuAutoScalingGroup[] {\n return cdkStack.node.findAll().filter((_) => _ instanceof GuAutoScalingGroup) as GuAutoScalingGroup[];\n }\n\n // eslint-disable-next-line custom-rules/valid-constructors -- this needs to sit above GuStack on the cdk tree\n constructor(app: App) {\n this.allCdkStacks = app.node.findAll().filter((_) => _ instanceof GuStack) as GuStack[];\n const allowedStages = new Set(this.allCdkStacks.map(({ stage }) => stage));\n this.allStageTags = Array.from(allowedStages);\n this.allStackTags = Array.from(new Set(this.allCdkStacks.map(({ stack }) => stack)));\n this.allRegions = Array.from(new Set(this.allCdkStacks.map(({ region }) => region)));\n\n this.validateStacksInApp();\n this.validateAllRegionsAreResolved();\n\n this.outdir = app.outdir;\n\n const deployments = new Map<RiffRaffDeploymentName, RiffRaffDeploymentProps>();\n\n const groupedStacks: GroupedCdkStacks = groupByClassNameStackRegionStage(this.allCdkStacks);\n\n Object.values(groupedStacks).forEach((stackTagGroup) => {\n Object.values(stackTagGroup).forEach((regionGroup) => {\n Object.values(regionGroup).forEach((stageGroup) => {\n const stacks: GuStack[] = Object.values(stageGroup).flat();\n\n if (stacks.length === 0) {\n throw new Error(\"Unable to produce a working riff-raff.yaml file; there are no stacks!\");\n }\n\n // The items in `stacks` only differ by stage, so we can just use the first item in the list.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- length of `stacks` is checked above\n const stack = stacks[0]!;\n\n const lambdas = this.getLambdas(stack);\n const autoscalingGroups = this.getAutoScalingGroups(stack);\n\n const artifactUploads: RiffRaffDeployment[] = [\n lambdas.map(uploadLambdaArtifact),\n autoscalingGroups.map(uploadAutoscalingArtifact),\n ].flat();\n artifactUploads.forEach(({ name, props }) => deployments.set(name, props));\n\n const cfnDeployment = cloudFormationDeployment(stacks, artifactUploads, this.outdir);\n deployments.set(cfnDeployment.name, cfnDeployment.props);\n\n lambdas.forEach((lambda) => {\n const lambdaDeployment = updateLambdaDeployment(lambda, cfnDeployment);\n deployments.set(lambdaDeployment.name, lambdaDeployment.props);\n });\n\n autoscalingGroups.forEach((asg) => {\n const asgDeployment = autoscalingDeployment(asg, cfnDeployment);\n deployments.set(asgDeployment.name, asgDeployment.props);\n\n deployments.set(cfnDeployment.name, addAmiParametersToCloudFormationDeployment(cfnDeployment, asg));\n });\n });\n });\n });\n\n this.riffRaffYaml = {\n allowedStages,\n deployments,\n };\n }\n\n /**\n * The `riff-raff.yaml` file as a string.\n * Useful for testing.\n */\n toYAML(): string {\n // Add support for ES6 Set and Map. See https://github.com/nodeca/js-yaml/issues/436.\n const replacer = (_key: string, value: unknown) => {\n if (value instanceof Set) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- this is how `js-yaml` is typed\n return Array.from(value);\n }\n if (value instanceof Map) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- this is how `js-yaml` is typed\n return Object.fromEntries(value);\n }\n return value;\n };\n\n return dump(this.riffRaffYaml, { replacer });\n }\n\n /**\n * Write the `riff-raff.yaml` file to disk.\n * It'll be located with the CFN JSON templates generated by `cdk synth`.\n */\n synth(): void {\n const outPath = path.join(this.outdir, \"riff-raff.yaml\");\n writeFileSync(outPath, this.toYAML());\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/experimental/riff-raff-yaml-file/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeFileSync } from \"fs\";\nimport path from \"path\";\nimport type { App } from \"aws-cdk-lib\";\nimport { Token } from \"aws-cdk-lib\";\nimport chalk from \"chalk\";\nimport { dump } from \"js-yaml\";\nimport { GuAutoScalingGroup } from \"../../constructs/autoscaling\";\nimport { GuStack } from \"../../constructs/core\";\nimport { GuLambdaFunction } from \"../../constructs/lambda\";\nimport { autoscalingDeployment, uploadAutoscalingArtifact } from \"./deployments/autoscaling\";\nimport { addAmiParametersToCloudFormationDeployment, cloudFormationDeployment } from \"./deployments/cloudformation\";\nimport { updateLambdaDeployment, uploadLambdaArtifact } from \"./deployments/lambda\";\nimport { groupByClassNameStackRegionStage } from \"./group-by\";\nimport type {\n ClassName,\n GroupedCdkStacks,\n Region,\n RiffRaffDeployment,\n RiffRaffDeploymentName,\n RiffRaffDeploymentProps,\n RiffRaffYaml,\n StackTag,\n StageTag,\n} from \"./types\";\n\n/**\n * A class that creates a `riff-raff.yaml` file.\n *\n * Rather than using this directly, prefer to use [[`GuRoot`]] instead.\n *\n * Supports:\n * - Multiple CloudFormation stacks\n * - Multiple regions\n * - Lambda applications\n * - EC2 (autoscaling) applications\n *\n * For lambda applications, 3 deployments will be defined:\n * 1. Lambda upload (`aws-lambda`, `action: [uploadLambda]`)\n * 2. CloudFormation deploy (`cloud-formation`)\n * 3. Lambda update (`aws-lambda`, `action: [updateLambda]`)\n *\n * For EC2 applications, 3 deployments will be defined:\n * 1. Artifact upload (`autoscaling`, `action: [uploadArtifacts]`)\n * 2. CloudFormation deploy (`cloud-formation`)\n * 3. Autoscaling deploy (`autoscaling`, `action: [deploy]`)\n *\n * It assumes a Riff-Raff bundle structure as follows:\n *\n * ```\n * .\n * ├── cdk.out\n * │ └── MyApplication.template.json\n * ├── my-application\n * │ └── my-application.deb\n * └── my-lambda\n * └── my-lambda.zip\n * ```\n *\n * That is, all CloudFormation templates are in a `cdk.out` directory, and there is a directory per app.\n * Application artifact(s) are in the app directory.\n *\n * NOTE: The file extension is decided by you, the above file tree is used for illustration purposes.\n *\n * NOTE: Resources will be looked up by tags (Stack, Stage, App). Ensure your CFN stack is tagged appropriately!\n *\n * @see https://riffraff.gutools.co.uk/docs/reference/riff-raff.yaml.md\n * @see https://riffraff.gutools.co.uk/docs/magenta-lib/types\n */\nexport class RiffRaffYamlFileExperimental {\n private readonly allCdkStacks: GuStack[];\n private readonly allStackTags: StackTag[];\n private readonly allStageTags: StageTag[];\n private readonly allRegions: Region[];\n\n private readonly riffRaffYaml: RiffRaffYaml;\n private readonly outdir: string;\n\n private isCdkStackPresent(\n expectedClassName: ClassName,\n expectedStack: StackTag,\n expectedRegion: Region,\n expectedStage: StageTag\n ): boolean {\n const matches = this.allCdkStacks.find((cdkStack) => {\n const {\n constructor: { name },\n stack,\n stage,\n region,\n } = cdkStack;\n return (\n name === expectedClassName && stack === expectedStack && stage === expectedStage && region === expectedRegion\n );\n });\n\n return !!matches;\n }\n\n /**\n * Check there are the appropriate number of `GuStack`s.\n * Expect to find an instance for each combination of `GuStack`, `stack`, `region`, and `stage`.\n *\n * If not valid, a message is logged describing what is missing to aid debugging.\n *\n * Given the following:\n *\n * ```ts\n * const app = new App();\n *\n * class MyApplicationStack extends GuStack { }\n *\n * new MyApplicationStack(app, \"App-CODE-deploy\", {\n * env: {\n * region: \"eu-west-1\",\n * },\n * stack: \"deploy\",\n * stage: \"CODE\"\n * });\n *\n * new MyApplicationStack(app, \"App-PROD-media-service\", {\n * env: {\n * region: \"eu-west-1\",\n * },\n * stack: \"media-service\",\n * stage: \"PROD\",\n * });\n *\n * new MyApplicationStack(app, \"App-PROD-deploy\", {\n * env: {\n * region: \"eu-west-1\",\n * },\n * stack: \"deploy\",\n * stage: \"PROD\"\n * });\n * ```\n *\n * This will log a message like this, where ❌ denotes something missing,\n * specifically there is no `CODE` template for `media-service`.\n *\n * ```log\n * Unable to produce a working riff-raff.yaml file; missing 1 definitions (details below)\n *\n * For the class: MyApplicationStack\n *\n * ┌───────────────┬────────────────────────────┐\n * │ (index) │ eu-west-1 │\n * ├───────────────┼────────────────────────────┤\n * │ deploy │ { CODE: '✅', PROD: '✅' } │\n * │ media-service │ { CODE: '❌', PROD: '✅' } │\n * └───────────────┴────────────────────────────┘\n * ```\n *\n * @private\n */\n private validateStacksInApp(): void {\n type Found = \"✅\";\n type NotFound = \"❌\";\n type AppValidation = Record<ClassName, Record<StackTag, Record<Region, Record<StageTag, Found | NotFound>>>>;\n\n const { allCdkStacks, allStackTags, allStageTags, allRegions } = this;\n\n const checks: AppValidation = allCdkStacks.reduce((accClassName, { constructor: { name } }) => {\n return {\n ...accClassName,\n [name]: allStackTags.reduce((accStackTag, stackTag) => {\n return {\n ...accStackTag,\n [stackTag]: allRegions.reduce((accRegion, region) => {\n return {\n ...accRegion,\n [region]: allStageTags.reduce((accStageTag, stageTag) => {\n return {\n ...accStageTag,\n [stageTag]: this.isCdkStackPresent(name, stackTag, region, stageTag) ? \"✅\" : \"❌\",\n };\n }, {}),\n };\n }, {}),\n };\n }, {}),\n };\n }, {});\n\n const missingDefinitions = Object.values(checks).flatMap((groupedByStackTag) => {\n return Object.values(groupedByStackTag).flatMap((groupedByRegion) => {\n return Object.values(groupedByRegion).flatMap((groupedByStage) => {\n return Object.values(groupedByStage).filter((_) => _ === \"❌\");\n });\n });\n });\n\n if (missingDefinitions.length > 0) {\n const message = `Unable to produce a working riff-raff.yaml file; missing ${missingDefinitions.length} definitions`;\n\n console.log(`${message} (details below)`);\n Object.entries(checks).forEach(([className, detail]) => {\n console.log(`For the class: ${chalk.yellow(className)}`);\n console.table(detail);\n });\n\n throw new Error(message);\n }\n }\n\n private validateAllRegionsAreResolved(): void {\n const unresolved = this.allRegions.filter((region) => Token.isUnresolved(region));\n\n if (unresolved.length !== 0) {\n throw new Error(`Unable to produce a working riff-raff.yaml file; all stacks must have an explicit region set`);\n }\n }\n\n private getLambdas(cdkStack: GuStack): GuLambdaFunction[] {\n return cdkStack.node.findAll().filter((_) => _ instanceof GuLambdaFunction) as GuLambdaFunction[];\n }\n\n private getAutoScalingGroups(cdkStack: GuStack): GuAutoScalingGroup[] {\n return cdkStack.node.findAll().filter((_) => _ instanceof GuAutoScalingGroup) as GuAutoScalingGroup[];\n }\n\n // eslint-disable-next-line custom-rules/valid-constructors -- this needs to sit above GuStack on the cdk tree\n constructor(app: App) {\n this.allCdkStacks = app.node.findAll().filter((_) => _ instanceof GuStack) as GuStack[];\n const allowedStages = new Set(this.allCdkStacks.map(({ stage }) => stage));\n this.allStageTags = Array.from(allowedStages);\n this.allStackTags = Array.from(new Set(this.allCdkStacks.map(({ stack }) => stack)));\n this.allRegions = Array.from(new Set(this.allCdkStacks.map(({ region }) => region)));\n\n this.validateStacksInApp();\n this.validateAllRegionsAreResolved();\n\n this.outdir = app.outdir;\n\n const deployments = new Map<RiffRaffDeploymentName, RiffRaffDeploymentProps>();\n\n const groupedStacks: GroupedCdkStacks = groupByClassNameStackRegionStage(this.allCdkStacks);\n\n Object.values(groupedStacks).forEach((stackTagGroup) => {\n Object.values(stackTagGroup).forEach((regionGroup) => {\n Object.values(regionGroup).forEach((stageGroup) => {\n const stacks: GuStack[] = Object.values(stageGroup).flat();\n\n if (stacks.length === 0) {\n throw new Error(\"Unable to produce a working riff-raff.yaml file; there are no stacks!\");\n }\n\n // The items in `stacks` only differ by stage, so we can just use the first item in the list.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- length of `stacks` is checked above\n const stack = stacks[0]!;\n\n const lambdas = this.getLambdas(stack);\n const autoscalingGroups = this.getAutoScalingGroups(stack);\n\n const artifactUploads: RiffRaffDeployment[] = [\n lambdas.map(uploadLambdaArtifact),\n autoscalingGroups.map(uploadAutoscalingArtifact),\n ].flat();\n artifactUploads.forEach(({ name, props }) => deployments.set(name, props));\n\n const cfnDeployment = cloudFormationDeployment(stacks, artifactUploads, this.outdir);\n deployments.set(cfnDeployment.name, cfnDeployment.props);\n\n lambdas.forEach((lambda) => {\n const lambdaDeployment = updateLambdaDeployment(lambda, cfnDeployment);\n deployments.set(lambdaDeployment.name, lambdaDeployment.props);\n });\n\n autoscalingGroups.forEach((asg) => {\n const asgDeployment = autoscalingDeployment(asg, cfnDeployment);\n deployments.set(asgDeployment.name, asgDeployment.props);\n\n deployments.set(cfnDeployment.name, addAmiParametersToCloudFormationDeployment(cfnDeployment, asg));\n });\n });\n });\n });\n\n this.riffRaffYaml = {\n allowedStages,\n deployments,\n };\n }\n\n /**\n * The `riff-raff.yaml` file as a string.\n * Useful for testing.\n */\n toYAML(): string {\n // Add support for ES6 Set and Map. See https://github.com/nodeca/js-yaml/issues/436.\n const replacer = (_key: string, value: unknown) => {\n if (value instanceof Set) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- this is how `js-yaml` is typed\n return Array.from(value);\n }\n if (value instanceof Map) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- this is how `js-yaml` is typed\n return Object.fromEntries(value);\n }\n return value;\n };\n\n return dump(this.riffRaffYaml, { replacer });\n }\n\n /**\n * Write the `riff-raff.yaml` file to disk.\n * It'll be located with the CFN JSON templates generated by `cdk synth`.\n */\n synth(): void {\n const outPath = path.join(this.outdir, \"riff-raff.yaml\");\n writeFileSync(outPath, this.toYAML());\n }\n}\n"]}
@@ -1,14 +1,14 @@
1
1
  import type { GuStack } from "../../constructs/core";
2
- export declare type ClassName = string;
3
- export declare type StackTag = string;
4
- export declare type StageTag = string;
5
- export declare type Region = string;
6
- export declare type CdkStacksDifferingOnlyByStage = GuStack[];
2
+ export type ClassName = string;
3
+ export type StackTag = string;
4
+ export type StageTag = string;
5
+ export type Region = string;
6
+ export type CdkStacksDifferingOnlyByStage = GuStack[];
7
7
  export interface RiffRaffYaml {
8
8
  allowedStages: Set<StageTag>;
9
9
  deployments: Map<RiffRaffDeploymentName, RiffRaffDeploymentProps>;
10
10
  }
11
- export declare type RiffRaffDeploymentName = string;
11
+ export type RiffRaffDeploymentName = string;
12
12
  export interface RiffRaffDeploymentProps {
13
13
  type: string;
14
14
  regions: Set<Region>;
@@ -23,5 +23,5 @@ export interface RiffRaffDeployment {
23
23
  name: RiffRaffDeploymentName;
24
24
  props: RiffRaffDeploymentProps;
25
25
  }
26
- export declare type GroupedCdkStacks = Record<ClassName, Record<StackTag, Record<Region, Record<StageTag, CdkStacksDifferingOnlyByStage>>>>;
26
+ export type GroupedCdkStacks = Record<ClassName, Record<StackTag, Record<Region, Record<StageTag, CdkStacksDifferingOnlyByStage>>>>;
27
27
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/experimental/riff-raff-yaml-file/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,oBAAY,SAAS,GAAG,MAAM,CAAC;AAC/B,oBAAY,QAAQ,GAAG,MAAM,CAAC;AAC9B,oBAAY,QAAQ,GAAG,MAAM,CAAC;AAC9B,oBAAY,MAAM,GAAG,MAAM,CAAC;AAC5B,oBAAY,6BAA6B,GAAG,OAAO,EAAE,CAAC;AAEtD,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,sBAAsB,EAAE,uBAAuB,CAAC,CAAC;CACnE;AAED,oBAAY,sBAAsB,GAAG,MAAM,CAAC;AAE5C,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,YAAY,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,KAAK,EAAE,uBAAuB,CAAC;CAChC;AASD,oBAAY,gBAAgB,GAAG,MAAM,CACnC,SAAS,EACT,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,6BAA6B,CAAC,CAAC,CAAC,CAClF,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/experimental/riff-raff-yaml-file/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC9B,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC9B,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAC5B,MAAM,MAAM,6BAA6B,GAAG,OAAO,EAAE,CAAC;AAEtD,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,sBAAsB,EAAE,uBAAuB,CAAC,CAAC;CACnE;AAED,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE5C,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,YAAY,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,KAAK,EAAE,uBAAuB,CAAC;CAChC;AASD,MAAM,MAAM,gBAAgB,GAAG,MAAM,CACnC,SAAS,EACT,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,6BAA6B,CAAC,CAAC,CAAC,CAClF,CAAC"}
@@ -4,7 +4,7 @@ import { Construct } from "constructs";
4
4
  import type { Http5xxAlarmProps, NoMonitoring } from "../constructs/cloudwatch";
5
5
  import type { AppIdentity, GuStack } from "../constructs/core";
6
6
  import type { GuLambdaFunction } from "../constructs/lambda";
7
- export declare type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE" | "PATCH";
7
+ export type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE" | "PATCH";
8
8
  export interface ApiTarget {
9
9
  /**
10
10
  * The path for the request (e.g. /test).
@@ -1 +1 @@
1
- {"version":3,"file":"api-multiple-lambdas.d.ts","sourceRoot":"","sources":["../../src/patterns/api-multiple-lambdas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAqB,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAEhF,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,oBAAY,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAEhH,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,UAAU,EAAE,UAAU,CAAC;IACvB;;OAEG;IACH,MAAM,EAAE,gBAAgB,CAAC;IACzB;;OAEG;IACH,cAAc,CAAC,EAAE,IAAI,CAAC;CACvB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,iBAAiB,CAAC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,iCAAkC,SAAQ,YAAY,EAAE,WAAW;IAClF;;OAEG;IACH,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB;;;;;;;OAOG;IACH,uBAAuB,EAAE,YAAY,GAAG,gBAAgB,CAAC;CAC1D;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,qBAAa,4BAA6B,SAAQ,SAAS;IACzD,SAAgB,GAAG,EAAE,OAAO,CAAC;gBAEjB,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,iCAAiC;CAwBrE"}
1
+ {"version":3,"file":"api-multiple-lambdas.d.ts","sourceRoot":"","sources":["../../src/patterns/api-multiple-lambdas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAqB,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAEhF,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAEhH,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,UAAU,EAAE,UAAU,CAAC;IACvB;;OAEG;IACH,MAAM,EAAE,gBAAgB,CAAC;IACzB;;OAEG;IACH,cAAc,CAAC,EAAE,IAAI,CAAC;CACvB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,iBAAiB,CAAC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,iCAAkC,SAAQ,YAAY,EAAE,WAAW;IAClF;;OAEG;IACH,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB;;;;;;;OAOG;IACH,uBAAuB,EAAE,YAAY,GAAG,gBAAgB,CAAC;CAC1D;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,qBAAa,4BAA6B,SAAQ,SAAS;IACzD,SAAgB,GAAG,EAAE,OAAO,CAAC;gBAEjB,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,iCAAiC;CAwBrE"}
@@ -45,26 +45,6 @@ export interface Alarms {
45
45
  unhealthyInstancesAlarm: boolean;
46
46
  noMonitoring?: false;
47
47
  }
48
- /**
49
- * Use this to enable Google Auth for your service.
50
- *
51
- * As part of using this, you'll need to do two things:
52
- *
53
- * 1. Create a Google app and generate OAuth credentials for it. Store the
54
- * `clientSecret` in AWS Secrets Manager. See the `clientSecretPath` prop for
55
- * the path to use here (and override the default if required).
56
- * 2. Update your app to validate the JWT token, which gets passed via the
57
- * `x-amzn-oidc-data` header. You MUST check that the token is a valid JWT
58
- * token - specifically, that it was encoded using the `ES256` algorithm and
59
- * that the contained `email` claim is a @guardian.co.uk email address.
60
- *
61
- * For more information, see:
62
- * https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-authenticate-users.html.
63
- */
64
- export interface GoogleAuthProps {
65
- clientIdPath?: string;
66
- clientSecretPath?: string;
67
- }
68
48
  /**
69
49
  * Configuration options for the [[`GuEc2App`]] pattern.
70
50
  *
@@ -149,7 +129,6 @@ export interface GuEc2AppProps extends AppIdentity {
149
129
  vpc?: IVpc;
150
130
  privateSubnets?: ISubnet[];
151
131
  publicSubnets?: ISubnet[];
152
- googleAuth?: GoogleAuthProps;
153
132
  }
154
133
  /**
155
134
  * Pattern which creates the resources needed to run an application on EC2 instances.
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/patterns/ec2-app/base.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE/D,OAAO,KAAK,EAAE,YAAY,EAAS,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAK9E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAc,MAAM,8BAA8B,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEnF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAmD,MAAM,uBAAuB,CAAC;AAErG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,0BAA0B,EAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,MAAM;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,KAAK,GAAG,iBAAiB,CAAC;IACxC,uBAAuB,EAAE,OAAO,CAAC;IACjC,YAAY,CAAC,EAAE,KAAK,CAAC;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,eAAe;IAG9B,YAAY,CAAC,EAAE,MAAM,CAAC;IAItB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,QAAQ,EAAE,eAAe,GAAG,MAAM,CAAC;IACnC,MAAM,EAAE,SAAS,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,uBAAuB,EAAE,MAAM,GAAG,YAAY,CAAC;IAC/C,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;IAC7B,OAAO,EAAE,aAAa,CAAC;IACvB,gBAAgB,EAAE,YAAY,CAAC;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC;IAC1B,UAAU,CAAC,EAAE,eAAe,CAAC;CAC9B;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2KG;AACH,qBAAa,QAAS,SAAQ,SAAS;IASrC,SAAgB,GAAG,EAAE,IAAI,CAAC;IAC1B,SAAgB,WAAW,EAAE,aAAa,CAAC;IAC3C,SAAgB,YAAY,EAAE,yBAAyB,CAAC;IACxD,SAAgB,gBAAgB,EAAE,kBAAkB,CAAC;IACrD,SAAgB,QAAQ,EAAE,0BAA0B,CAAC;IACrD,SAAgB,WAAW,EAAE,wBAAwB,CAAC;gBAE1C,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa;CA+MjD"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/patterns/ec2-app/base.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE/D,OAAO,KAAK,EAAE,YAAY,EAAS,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAI9E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAc,MAAM,8BAA8B,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEnF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAmD,MAAM,uBAAuB,CAAC;AAErG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,0BAA0B,EAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,MAAM;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,KAAK,GAAG,iBAAiB,CAAC;IACxC,uBAAuB,EAAE,OAAO,CAAC;IACjC,YAAY,CAAC,EAAE,KAAK,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,QAAQ,EAAE,eAAe,GAAG,MAAM,CAAC;IACnC,MAAM,EAAE,SAAS,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,uBAAuB,EAAE,MAAM,GAAG,YAAY,CAAC;IAC/C,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;IAC7B,OAAO,EAAE,aAAa,CAAC;IACvB,gBAAgB,EAAE,YAAY,CAAC;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC;CAC3B;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2KG;AACH,qBAAa,QAAS,SAAQ,SAAS;IASrC,SAAgB,GAAG,EAAE,IAAI,CAAC;IAC1B,SAAgB,WAAW,EAAE,aAAa,CAAC;IAC3C,SAAgB,YAAY,EAAE,yBAAyB,CAAC;IACxD,SAAgB,gBAAgB,EAAE,kBAAkB,CAAC;IACrD,SAAgB,QAAQ,EAAE,0BAA0B,CAAC;IACrD,SAAgB,WAAW,EAAE,wBAAwB,CAAC;gBAE1C,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa;CAsKjD"}
@@ -6,7 +6,6 @@ const aws_autoscaling_1 = require("aws-cdk-lib/aws-autoscaling");
6
6
  const aws_ec2_1 = require("aws-cdk-lib/aws-ec2");
7
7
  const aws_elasticloadbalancingv2_1 = require("aws-cdk-lib/aws-elasticloadbalancingv2");
8
8
  const aws_s3_1 = require("aws-cdk-lib/aws-s3");
9
- const aws_ssm_1 = require("aws-cdk-lib/aws-ssm");
10
9
  const constructs_1 = require("constructs");
11
10
  const constants_1 = require("../../constants");
12
11
  const acm_1 = require("../../constructs/acm");
@@ -235,11 +234,6 @@ class GuEc2App extends constructs_1.Construct {
235
234
  ...(blockDevices && { blockDevices }),
236
235
  imageRecipe,
237
236
  });
238
- // We are selectively tagging the ASG so that we can expose the number of stacks using this pattern
239
- // based on which autoscaling groups possess this tag. It may become useful or necessary to tag all resources in the future,
240
- // but we have decided that this is sufficient for now.
241
- // TODO: Do we need to tag all resources with this value? What would the use-cases be?
242
- aws_cdk_lib_1.Tags.of(autoScalingGroup).add(constants_1.MetadataKeys.PATTERN_NAME, this.constructor.name, { applyToLaunchedInstances: true });
243
237
  // This allows automatic shipping of instance Cloud Init logs when using the
244
238
  // `cdk-base` Amigo role on your AMI.
245
239
  aws_cdk_lib_1.Tags.of(autoScalingGroup).add(constants_1.MetadataKeys.LOG_KINESIS_STREAM_NAME, core_1.GuLoggingStreamNameParameter.getInstance(scope).valueAsString);
@@ -280,33 +274,6 @@ class GuEc2App extends constructs_1.Construct {
280
274
  // When open=true, AWS will create a security group which allows all inbound traffic over HTTPS
281
275
  open: access.scope === constants_1.AccessScope.PUBLIC,
282
276
  });
283
- if (props.googleAuth) {
284
- const configPrefix = `${scope.stage}/${scope.stack}/${app}`;
285
- const clientIdPath = props.googleAuth.clientIdPath ?? `/${configPrefix}/googleClientID`;
286
- const clientId = aws_ssm_1.StringParameter.fromStringParameterAttributes(this, "clientID", {
287
- parameterName: clientIdPath,
288
- }).stringValue;
289
- const secretPath = props.googleAuth.clientSecretPath ?? `${configPrefix}/clientSecret`;
290
- const clientSecret = aws_cdk_lib_1.SecretValue.secretsManager(secretPath);
291
- const authAction = aws_elasticloadbalancingv2_1.ListenerAction.authenticateOidc({
292
- next: aws_elasticloadbalancingv2_1.ListenerAction.forward([targetGroup]),
293
- clientId: clientId,
294
- clientSecret: clientSecret,
295
- scope: "openid email",
296
- // See the `hd` section of
297
- // https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters.
298
- // Note, this is NOT sufficient to ensure access is limited to Guardian
299
- // emails. Users should also validate the token and check the domain in
300
- // their app.
301
- authenticationRequestExtraParams: { hd: "guardian.co.uk" },
302
- authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
303
- issuer: "https://accounts.google.com",
304
- tokenEndpoint: "https://oauth2.googleapis.com/token",
305
- userInfoEndpoint: "https://openidconnect.googleapis.com/v1/userinfo",
306
- });
307
- listener.addAction("auth", { action: authAction });
308
- aws_cdk_lib_1.Tags.of(loadBalancer).add(constants_1.MetadataKeys.CDK_FEATURE, "google-auth");
309
- }
310
277
  // Since AWS won't create a security group automatically when open=false, we need to add our own
311
278
  if (access.scope !== constants_1.AccessScope.PUBLIC) {
312
279
  loadBalancer.addSecurityGroup(new ec2_1.GuSecurityGroup(scope, `${access.scope}IngressSecurityGroup`, {