@fjall/components-infrastructure 3.1.0 → 3.2.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/dist/lib/app.d.ts +7 -0
- package/dist/lib/app.js +14 -0
- package/dist/lib/config/aws/alarmTopic.d.ts +7 -0
- package/dist/lib/config/aws/alarmTopic.js +9 -2
- package/dist/lib/patterns/aws/apexDomainPattern.d.ts +7 -7
- package/dist/lib/patterns/aws/apexDomainPattern.js +10 -14
- package/dist/lib/patterns/aws/buildkite/alarms.d.ts +5 -0
- package/dist/lib/patterns/aws/buildkite/alarms.js +3 -3
- package/dist/lib/patterns/aws/buildkite/buildkite.js +13 -6
- package/dist/lib/patterns/aws/buildkite/iam.js +2 -1
- package/dist/lib/patterns/aws/buildkite/schema.d.ts +1 -0
- package/dist/lib/patterns/aws/buildkite/schema.js +7 -0
- package/dist/lib/patterns/aws/buildkite.d.ts +0 -1
- package/dist/lib/patterns/aws/buildkite.js +0 -1
- package/dist/lib/patterns/aws/devSubstrate.d.ts +26 -15
- package/dist/lib/patterns/aws/devSubstrate.js +47 -27
- package/dist/lib/resources/aws/monitoring/buildkiteAlarms.d.ts +29 -0
- package/dist/lib/resources/aws/monitoring/buildkiteAlarms.js +90 -0
- package/dist/lib/resources/aws/monitoring/index.d.ts +1 -0
- package/dist/lib/resources/aws/monitoring/index.js +1 -0
- package/dist/lib/resources/aws/networking/hostedZone.js +23 -19
- package/package.json +4 -4
package/dist/lib/app.d.ts
CHANGED
|
@@ -292,6 +292,13 @@ export declare class App extends CdkApp {
|
|
|
292
292
|
* the fleet's environment tag matches the deploying config rather than the
|
|
293
293
|
* construct's `management` fallback; an explicit
|
|
294
294
|
* `props.costAllocationEnvironment` still wins.
|
|
295
|
+
*
|
|
296
|
+
* Alarm actions default to the account's `SharedAlarmTopicArn` export
|
|
297
|
+
* (design § D14 — alerting ships WITH the fleet): every fjall-governed
|
|
298
|
+
* account's Account/Platform stack produces that export, so alarms page
|
|
299
|
+
* out of the box. An explicit `props.alarmSnsTopicArn` still wins.
|
|
300
|
+
* `applicationId` defaults to the app name so the alarm webhook can map
|
|
301
|
+
* pages to the application; an explicit prop still wins.
|
|
295
302
|
*/
|
|
296
303
|
addBuildkite(props: BuildkitePropsInput): Buildkite;
|
|
297
304
|
/**
|
package/dist/lib/app.js
CHANGED
|
@@ -16,6 +16,7 @@ import { BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP } from "./utils/backupTierMapp
|
|
|
16
16
|
import { randomBytes } from "crypto";
|
|
17
17
|
import { getConfig } from "./utils/getConfig.js";
|
|
18
18
|
import { UNKNOWN_ENVIRONMENT } from "./utils/env.js";
|
|
19
|
+
import { SHARED_ALARM_TOPIC_EXPORT_NAME } from "./config/aws/alarmTopic.js";
|
|
19
20
|
import AuditRoleFactory from "./resources/aws/audit/auditRole.js";
|
|
20
21
|
import { FJALL_AUDIT_CONFIG } from "./config/audit.js";
|
|
21
22
|
import { FjallLogger } from "./utils/validationLogger.js";
|
|
@@ -496,6 +497,13 @@ export class App extends CdkApp {
|
|
|
496
497
|
* the fleet's environment tag matches the deploying config rather than the
|
|
497
498
|
* construct's `management` fallback; an explicit
|
|
498
499
|
* `props.costAllocationEnvironment` still wins.
|
|
500
|
+
*
|
|
501
|
+
* Alarm actions default to the account's `SharedAlarmTopicArn` export
|
|
502
|
+
* (design § D14 — alerting ships WITH the fleet): every fjall-governed
|
|
503
|
+
* account's Account/Platform stack produces that export, so alarms page
|
|
504
|
+
* out of the box. An explicit `props.alarmSnsTopicArn` still wins.
|
|
505
|
+
* `applicationId` defaults to the app name so the alarm webhook can map
|
|
506
|
+
* pages to the application; an explicit prop still wins.
|
|
499
507
|
*/
|
|
500
508
|
addBuildkite(props) {
|
|
501
509
|
const computeStack = this.getDefaultComputeStack();
|
|
@@ -508,6 +516,12 @@ export class App extends CdkApp {
|
|
|
508
516
|
configEnvironment !== UNKNOWN_ENVIRONMENT) {
|
|
509
517
|
constructProps.costAllocationEnvironment = configEnvironment;
|
|
510
518
|
}
|
|
519
|
+
if (constructProps.alarmSnsTopicArn === undefined) {
|
|
520
|
+
constructProps.alarmSnsTopicArn = `import:${SHARED_ALARM_TOPIC_EXPORT_NAME}`;
|
|
521
|
+
}
|
|
522
|
+
if (constructProps.applicationId === undefined) {
|
|
523
|
+
constructProps.applicationId = this.getName();
|
|
524
|
+
}
|
|
511
525
|
const fleet = new Buildkite(computeStack.getStack(), "Buildkite", constructProps);
|
|
512
526
|
computeStack.addConstruct(fleet);
|
|
513
527
|
return fleet;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { CfnOutput } from "aws-cdk-lib";
|
|
2
2
|
import type { ITopic } from "aws-cdk-lib/aws-sns";
|
|
3
3
|
import { Construct } from "constructs";
|
|
4
|
+
/**
|
|
5
|
+
* The well-known cross-stack export name for the account's shared alarm
|
|
6
|
+
* topic. Consumers build `"import:" + SHARED_ALARM_TOPIC_EXPORT_NAME`
|
|
7
|
+
* strings for `resolveAlertsTopic`; the CfnOutput below is the single
|
|
8
|
+
* producer.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SHARED_ALARM_TOPIC_EXPORT_NAME = "SharedAlarmTopicArn";
|
|
4
11
|
export declare class SharedAlarmTopic extends Construct {
|
|
5
12
|
readonly topic: ITopic;
|
|
6
13
|
readonly topicArn: CfnOutput;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { CfnOutput } from "aws-cdk-lib";
|
|
2
2
|
import { Construct } from "constructs";
|
|
3
3
|
import { SNSTopic } from "../../resources/aws/messaging/sns.js";
|
|
4
|
+
/**
|
|
5
|
+
* The well-known cross-stack export name for the account's shared alarm
|
|
6
|
+
* topic. Consumers build `"import:" + SHARED_ALARM_TOPIC_EXPORT_NAME`
|
|
7
|
+
* strings for `resolveAlertsTopic`; the CfnOutput below is the single
|
|
8
|
+
* producer.
|
|
9
|
+
*/
|
|
10
|
+
export const SHARED_ALARM_TOPIC_EXPORT_NAME = "SharedAlarmTopicArn";
|
|
4
11
|
export class SharedAlarmTopic extends Construct {
|
|
5
12
|
topic;
|
|
6
13
|
topicArn;
|
|
@@ -15,9 +22,9 @@ export class SharedAlarmTopic extends Construct {
|
|
|
15
22
|
// thinking it duplicates the wrapper's AlarmNotificationsTopicArn —
|
|
16
23
|
// the wrapper's auto-output has no exportName and cannot be imported.
|
|
17
24
|
this.topicArn = new CfnOutput(this, "SharedAlarmTopicArn", {
|
|
18
|
-
key:
|
|
25
|
+
key: SHARED_ALARM_TOPIC_EXPORT_NAME,
|
|
19
26
|
value: wrapped.getTopicArn(),
|
|
20
|
-
exportName:
|
|
27
|
+
exportName: SHARED_ALARM_TOPIC_EXPORT_NAME
|
|
21
28
|
});
|
|
22
29
|
}
|
|
23
30
|
}
|
|
@@ -14,12 +14,12 @@ export interface ApexDomainPatternResult {
|
|
|
14
14
|
*
|
|
15
15
|
* Delegation direction (D8): child writes NS. The apex zone's contribution
|
|
16
16
|
* to delegation is the org-gated `DelegationRole` the `HostedZone` wrapper
|
|
17
|
-
* creates
|
|
18
|
-
* role by LITERAL ARN (`parentDelegationRoleArn`) and UPSERTs
|
|
19
|
-
* records into this zone via `CrossAccountZoneDelegationRecord`.
|
|
20
|
-
* parent-writes `delegations[]` path (NS records from
|
|
21
|
-
* child nameserver exports) was removed with the H13
|
|
22
|
-
* export-locked, child-first-deploy-ordered, and
|
|
23
|
-
* redeploy.
|
|
17
|
+
* creates for created AND adopted zones — the delegated child `Domain`
|
|
18
|
+
* assumes that role by LITERAL ARN (`parentDelegationRoleArn`) and UPSERTs
|
|
19
|
+
* its own NS records into this zone via `CrossAccountZoneDelegationRecord`.
|
|
20
|
+
* The old parent-writes `delegations[]` path (NS records from
|
|
21
|
+
* `Fn.importValue` of child nameserver exports) was removed with the H13
|
|
22
|
+
* legacy layer: it was export-locked, child-first-deploy-ordered, and
|
|
23
|
+
* removal required a parent redeploy.
|
|
24
24
|
*/
|
|
25
25
|
export declare function composeApexDomain(scope: Construct, props: Route53ApexProps): ApexDomainPatternResult;
|
|
@@ -2,32 +2,28 @@ import { HostedZone } from "../../resources/aws/networking/hostedZone.js";
|
|
|
2
2
|
import { DomainCertificate } from "../../resources/aws/networking/domainCertificate.js";
|
|
3
3
|
import { composeTypedDnsRecords } from "./dnsRecordComposer.js";
|
|
4
4
|
import { toPascalCase, getSafeZoneName } from "../../utils/capitaliseString.js";
|
|
5
|
-
import { resolveOrgId } from "../../utils/cdkContext.js";
|
|
6
5
|
/**
|
|
7
6
|
* Composition for `registrar: "route53"`. Creates (or imports) the apex
|
|
8
7
|
* `HostedZone` and composes all user records + certificates.
|
|
9
8
|
*
|
|
10
9
|
* Delegation direction (D8): child writes NS. The apex zone's contribution
|
|
11
10
|
* to delegation is the org-gated `DelegationRole` the `HostedZone` wrapper
|
|
12
|
-
* creates
|
|
13
|
-
* role by LITERAL ARN (`parentDelegationRoleArn`) and UPSERTs
|
|
14
|
-
* records into this zone via `CrossAccountZoneDelegationRecord`.
|
|
15
|
-
* parent-writes `delegations[]` path (NS records from
|
|
16
|
-
* child nameserver exports) was removed with the H13
|
|
17
|
-
* export-locked, child-first-deploy-ordered, and
|
|
18
|
-
* redeploy.
|
|
11
|
+
* creates for created AND adopted zones — the delegated child `Domain`
|
|
12
|
+
* assumes that role by LITERAL ARN (`parentDelegationRoleArn`) and UPSERTs
|
|
13
|
+
* its own NS records into this zone via `CrossAccountZoneDelegationRecord`.
|
|
14
|
+
* The old parent-writes `delegations[]` path (NS records from
|
|
15
|
+
* `Fn.importValue` of child nameserver exports) was removed with the H13
|
|
16
|
+
* legacy layer: it was export-locked, child-first-deploy-ordered, and
|
|
17
|
+
* removal required a parent redeploy.
|
|
19
18
|
*/
|
|
20
19
|
export function composeApexDomain(scope, props) {
|
|
21
20
|
const safeZone = toPascalCase(getSafeZoneName(props.zoneName));
|
|
22
|
-
// Gotcha:
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// this gate.
|
|
26
|
-
const inOrganisation = resolveOrgId(scope.node) !== undefined;
|
|
21
|
+
// Gotcha: `createDelegationRole` is deliberately omitted — the wrapper's
|
|
22
|
+
// org-gated default gives created AND adopted apex zones the DelegationRole;
|
|
23
|
+
// re-adding a create-path-only condition would strand adopted apexes (D8).
|
|
27
24
|
const hostedZoneConstruct = new HostedZone(scope, `${safeZone}HostedZone`, {
|
|
28
25
|
zoneName: props.zoneName,
|
|
29
26
|
hostedZoneId: props.hostedZoneId,
|
|
30
|
-
createDelegationRole: props.hostedZoneId === undefined && inOrganisation,
|
|
31
27
|
costAllocationEnvironment: props.costAllocationEnvironment,
|
|
32
28
|
costAllocationDomain: props.zoneName
|
|
33
29
|
});
|
|
@@ -3,6 +3,11 @@ export interface BuildkiteAlarmParams {
|
|
|
3
3
|
readonly buildkiteOrgSlug: string;
|
|
4
4
|
readonly buildkiteQueue: string;
|
|
5
5
|
readonly autoScalingGroupName: string;
|
|
6
|
+
/**
|
|
7
|
+
* Alarm-action destination, in either `resolveAlertsTopic` string shape:
|
|
8
|
+
* a literal `arn:...` or `"import:<ExportName>"` (e.g.
|
|
9
|
+
* `"import:SharedAlarmTopicArn"`). Omitted → alarms exist but page nobody.
|
|
10
|
+
*/
|
|
6
11
|
readonly alarmSnsTopicArn?: string;
|
|
7
12
|
}
|
|
8
13
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Duration } from "aws-cdk-lib";
|
|
2
2
|
import { Alarm, ComparisonOperator, MathExpression, Metric, TreatMissingData } from "aws-cdk-lib/aws-cloudwatch";
|
|
3
3
|
import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveAlertsTopic } from "../../../utils/resolveAlertsTopic.js";
|
|
5
5
|
/**
|
|
6
6
|
* Metrics namespace the buildkite-agent-scaler publishes to, dimensioned by
|
|
7
7
|
* {Org, Queue} — BOTH dimensions are required; querying Queue alone reads no
|
|
@@ -70,8 +70,8 @@ export function addBuildkiteAlarms(scope, params) {
|
|
|
70
70
|
evaluationPeriods: 3,
|
|
71
71
|
treatMissingData: TreatMissingData.NOT_BREACHING
|
|
72
72
|
});
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
const topic = resolveAlertsTopic(scope, "BuildkiteAlarmTopic", params.alarmSnsTopicArn);
|
|
74
|
+
if (topic !== undefined) {
|
|
75
75
|
heartbeatAlarm.addAlarmAction(new SnsAction(topic));
|
|
76
76
|
queuedAlarm.addAlarmAction(new SnsAction(topic));
|
|
77
77
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CfnOutput, Duration, Stack, Token } from "aws-cdk-lib";
|
|
1
|
+
import { CfnOutput, Duration, RemovalPolicy, Stack, Token } from "aws-cdk-lib";
|
|
2
2
|
import { InstanceArchitecture, InstanceType, MachineImage, SubnetType } from "aws-cdk-lib/aws-ec2";
|
|
3
3
|
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
4
4
|
import { Source } from "aws-cdk-lib/aws-s3-deployment";
|
|
@@ -8,8 +8,9 @@ import { Ec2Instance } from "../../../resources/aws/compute/ec2.js";
|
|
|
8
8
|
import { SamApplication } from "../../../resources/aws/compute/samApplication.js";
|
|
9
9
|
import { Policy } from "../../../resources/aws/iam/index.js";
|
|
10
10
|
import { BucketDeployment, S3Bucket } from "../../../resources/aws/storage/index.js";
|
|
11
|
+
import { createBuildkiteAlarms } from "../../../resources/aws/monitoring/index.js";
|
|
11
12
|
import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
|
|
12
|
-
import {
|
|
13
|
+
import { resolveAlertsTopic } from "../../../utils/resolveAlertsTopic.js";
|
|
13
14
|
import { buildAgentRole } from "./iam.js";
|
|
14
15
|
import { BUILDKITE_STACK_PINS, resolvePinnedAmiId, resolveScalerSarApplicationArn } from "./pins.js";
|
|
15
16
|
import { validateBuildkiteProps } from "./schema.js";
|
|
@@ -55,7 +56,10 @@ export class Buildkite extends Construct {
|
|
|
55
56
|
})
|
|
56
57
|
});
|
|
57
58
|
const artifactBucket = new S3Bucket(this, `${id}ArtifactBucket`);
|
|
58
|
-
|
|
59
|
+
// Secrets (SSH deploy keys, git credentials) must survive stack teardown
|
|
60
|
+
// and accidental overwrite; the artifact bucket is scratch output and
|
|
61
|
+
// keeps the wrapper's env-aware default (DESTROY + pre-empty off prod).
|
|
62
|
+
const managedSecretsBucket = new S3Bucket(this, `${id}ManagedSecretsBucket`, { versioned: true, removalPolicy: RemovalPolicy.RETAIN });
|
|
59
63
|
if (config.fjallApiKeySsmParameterName !== undefined) {
|
|
60
64
|
this.addFjallApiKeyEnvHook(id, managedSecretsBucket, config.fjallApiKeySsmParameterName, region);
|
|
61
65
|
}
|
|
@@ -163,12 +167,15 @@ export class Buildkite extends Construct {
|
|
|
163
167
|
costAllocationEnvironment: config.costAllocationEnvironment
|
|
164
168
|
})
|
|
165
169
|
});
|
|
166
|
-
|
|
170
|
+
const alertsTopic = resolveAlertsTopic(this, "BuildkiteAlarmTopic", config.alarmSnsTopicArn);
|
|
171
|
+
createBuildkiteAlarms({
|
|
172
|
+
scope: this,
|
|
167
173
|
buildkiteOrgSlug: config.buildkiteOrgSlug,
|
|
168
174
|
buildkiteQueue: config.buildkiteQueue,
|
|
169
175
|
autoScalingGroupName: autoScalingGroup.autoScalingGroupName,
|
|
170
|
-
...(
|
|
171
|
-
|
|
176
|
+
...(alertsTopic !== undefined && { alarmTopic: alertsTopic }),
|
|
177
|
+
...(config.applicationId !== undefined && {
|
|
178
|
+
applicationId: config.applicationId
|
|
172
179
|
})
|
|
173
180
|
});
|
|
174
181
|
this.artifactBucketName = artifactBucket.bucketName;
|
|
@@ -58,7 +58,8 @@ export const BUILDKITE_AGENT_IAM_ACTION_ALLOWLIST = [
|
|
|
58
58
|
*/
|
|
59
59
|
export function buildAgentRole(scope, id, params) {
|
|
60
60
|
return new Role(scope, id, {
|
|
61
|
-
|
|
61
|
+
// IAM descriptions only accept [\t\n\r\x20-\x7E\xA1-\xFF] — no em-dashes
|
|
62
|
+
description: "Buildkite agent instance role - allowlist-only, no deploy capability",
|
|
62
63
|
inlinePolicies: buildAgentPolicyDocuments(params),
|
|
63
64
|
assumedBy: new CompositePrincipal(new ServicePrincipal("autoscaling.amazonaws.com"), new ServicePrincipal("ec2.amazonaws.com"))
|
|
64
65
|
});
|
|
@@ -66,6 +66,7 @@ export declare const BuildkitePropsSchema: z.ZodObject<{
|
|
|
66
66
|
scaleOutWaitingForJobs: z.ZodDefault<z.ZodBoolean>;
|
|
67
67
|
rolePermissionsBoundaryArn: z.ZodOptional<z.ZodString>;
|
|
68
68
|
alarmSnsTopicArn: z.ZodOptional<z.ZodString>;
|
|
69
|
+
applicationId: z.ZodOptional<z.ZodString>;
|
|
69
70
|
costAllocationEnvironment: z.ZodOptional<z.ZodString>;
|
|
70
71
|
costAllocationOwner: z.ZodOptional<z.ZodString>;
|
|
71
72
|
}, z.core.$strict>;
|
|
@@ -124,6 +124,13 @@ export const BuildkitePropsSchema = z
|
|
|
124
124
|
* actions (visible on the console, silent).
|
|
125
125
|
*/
|
|
126
126
|
alarmSnsTopicArn: z.string().min(1).optional(),
|
|
127
|
+
/**
|
|
128
|
+
* Fjall application the fleet belongs to — appended to alarm
|
|
129
|
+
* descriptions and tagged (`fjall:applicationId`) so the alarm webhook
|
|
130
|
+
* can map pages to the application. `App.addBuildkite` injects the app
|
|
131
|
+
* name automatically; set only to override.
|
|
132
|
+
*/
|
|
133
|
+
applicationId: z.string().min(1).optional(),
|
|
127
134
|
costAllocationEnvironment: z.string().min(1).optional(),
|
|
128
135
|
costAllocationOwner: z.string().min(1).optional()
|
|
129
136
|
})
|
|
@@ -9,4 +9,3 @@ export { BUILDKITE_AMI_OWNER_ACCOUNT_ID, BUILDKITE_CPU_ARCHITECTURES, BUILDKITE_
|
|
|
9
9
|
export { BuildkitePropsSchema, validateBuildkiteProps, type BuildkiteProps, type BuildkitePropsInput } from "./buildkite/schema.js";
|
|
10
10
|
export { BUILDKITE_AGENT_IAM_ACTION_ALLOWLIST, buildAgentRole, type BuildkiteAgentRoleParams } from "./buildkite/iam.js";
|
|
11
11
|
export { BUILDKITE_DOCKER_PART_ENV_KEYS, BUILDKITE_MOUNT_PART_ENV_KEYS, BUILDKITE_USER_DATA_ENV_KEYS, buildBuildkiteDockerCommands, buildBuildkiteInstallCommands, buildBuildkiteMountCommands, buildBuildkiteUserData, type BuildkiteUserDataContext } from "./buildkite/userData.js";
|
|
12
|
-
export { addBuildkiteAlarms, type BuildkiteAlarmParams } from "./buildkite/alarms.js";
|
|
@@ -9,4 +9,3 @@ export { BUILDKITE_AMI_OWNER_ACCOUNT_ID, BUILDKITE_CPU_ARCHITECTURES, BUILDKITE_
|
|
|
9
9
|
export { BuildkitePropsSchema, validateBuildkiteProps } from "./buildkite/schema.js";
|
|
10
10
|
export { BUILDKITE_AGENT_IAM_ACTION_ALLOWLIST, buildAgentRole } from "./buildkite/iam.js";
|
|
11
11
|
export { BUILDKITE_DOCKER_PART_ENV_KEYS, BUILDKITE_MOUNT_PART_ENV_KEYS, BUILDKITE_USER_DATA_ENV_KEYS, buildBuildkiteDockerCommands, buildBuildkiteInstallCommands, buildBuildkiteMountCommands, buildBuildkiteUserData } from "./buildkite/userData.js";
|
|
12
|
-
export { addBuildkiteAlarms } from "./buildkite/alarms.js";
|
|
@@ -40,27 +40,38 @@ export interface IDevSubstrateProps {
|
|
|
40
40
|
readonly adoptSlotEcr?: boolean;
|
|
41
41
|
/**
|
|
42
42
|
* Deploy phase for the R2 two-step cert-hang guard. `"zone"` synthesises the
|
|
43
|
-
* `
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
43
|
+
* `devDomain` zone + cross-account NS delegation only; `"full"` additionally
|
|
44
|
+
* builds the wildcard cert + :443 listener (A6) — deploy that only after the
|
|
45
|
+
* delegated NS has propagated, else ACM DNS-validation hangs. Defaults to
|
|
46
|
+
* `"full"`. Inert unless `domain` is set.
|
|
47
47
|
*/
|
|
48
48
|
readonly phase?: DomainDeployPhase;
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* slots via
|
|
50
|
+
* The zone slots serve under — an EXPLICIT name (`devDomain`, e.g.
|
|
51
|
+
* `development.fjall.io`), never derived by prefixing. When omitted, the
|
|
52
|
+
* substrate stands up no zone/cert (early milestone — G2 reaches slots via
|
|
53
|
+
* `curl --resolve`).
|
|
53
54
|
*
|
|
54
|
-
* `
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* zone
|
|
55
|
+
* Adopt mode (`hostedZoneId` present): the substrate adopts an existing zone
|
|
56
|
+
* (e.g. the account's delegated Domain) by import — no zone resource, no
|
|
57
|
+
* delegation record, no NS export. Mutually exclusive with `parentDomain` /
|
|
58
|
+
* `parentDelegationRoleArn`.
|
|
59
|
+
*
|
|
60
|
+
* Create mode (no `hostedZoneId`): the substrate creates the `devDomain`
|
|
61
|
+
* zone. `parentDelegationRoleArn`, when present, wires the same-org **Case-1**
|
|
62
|
+
* in-stack NS delegation (design VD2-a): the child stack's
|
|
63
|
+
* `CrossAccountZoneDelegationRecord` assumes the parent account's
|
|
64
|
+
* `DelegationRole` by LITERAL ARN and UPSERTs the child NS into the
|
|
65
|
+
* `parentDomain` zone — `parentDomain` (a proper DNS suffix of `devDomain`)
|
|
66
|
+
* MUST accompany it. Omit both for a plain externally-owned parent (Case 3 —
|
|
67
|
+
* manual one-off delegation) or a connected different-org parent (Case 2 —
|
|
68
|
+
* worker UPSERT, B3b, deferred): the substrate then emits only the zone + its
|
|
69
|
+
* NS export for out-of-band delegation.
|
|
61
70
|
*/
|
|
62
71
|
readonly domain?: {
|
|
63
|
-
readonly
|
|
72
|
+
readonly devDomain: string;
|
|
73
|
+
readonly hostedZoneId?: string;
|
|
74
|
+
readonly parentDomain?: string;
|
|
64
75
|
readonly parentDelegationRoleArn?: string;
|
|
65
76
|
};
|
|
66
77
|
}
|
|
@@ -4,6 +4,7 @@ import { Cluster } from "aws-cdk-lib/aws-ecs";
|
|
|
4
4
|
import { Repository } from "aws-cdk-lib/aws-ecr";
|
|
5
5
|
import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
6
6
|
import { Code, Runtime } from "aws-cdk-lib/aws-lambda";
|
|
7
|
+
import { PublicHostedZone } from "aws-cdk-lib/aws-route53";
|
|
7
8
|
import { SqsDestination } from "aws-cdk-lib/aws-s3-notifications";
|
|
8
9
|
import { Construct } from "constructs";
|
|
9
10
|
import { readFileSync } from "node:fs";
|
|
@@ -48,7 +49,7 @@ const DEV_SUBSTRATE_WAKER_ID = "DevSubstrateWaker";
|
|
|
48
49
|
const DEV_SUBSTRATE_WAKER_TG_ID = "DevSubstrateWakerTargetGroup";
|
|
49
50
|
const DEV_SUBSTRATE_WAKER_SOURCE_FILE = "devSubstrate.waker.source.cjs";
|
|
50
51
|
const DEV_SUBSTRATE_WAKER_TIMEOUT_SECONDS = 30;
|
|
51
|
-
/** Construct ids for the substrate's
|
|
52
|
+
/** Construct ids for the substrate's `devDomain` slot zone (created or adopted). */
|
|
52
53
|
const DEV_SUBSTRATE_ZONE_ID = "DevSubstrateZone";
|
|
53
54
|
const DEV_SUBSTRATE_DELEGATION_ID = "DevSubstrateDelegation";
|
|
54
55
|
const DEV_SUBSTRATE_PARENT_ROLE_ID = "DevSubstrateParentDelegationRole";
|
|
@@ -306,32 +307,51 @@ export class DevSubstrate extends Construct {
|
|
|
306
307
|
#createDomain(props, loadBalancer) {
|
|
307
308
|
if (props.domain === undefined)
|
|
308
309
|
return {};
|
|
309
|
-
const {
|
|
310
|
-
const childZoneName =
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const parentRole = Role.fromRoleArn(this, DEV_SUBSTRATE_PARENT_ROLE_ID, parentDelegationRoleArn);
|
|
329
|
-
new CrossAccountDelegationRecord(this, DEV_SUBSTRATE_DELEGATION_ID, {
|
|
330
|
-
delegationRole: parentRole,
|
|
331
|
-
delegatedZone: childZone.hostedZone,
|
|
332
|
-
delegatedZoneName: childZoneName,
|
|
333
|
-
parentHostedZoneName: appDomain
|
|
310
|
+
const { devDomain, hostedZoneId, parentDomain, parentDelegationRoleArn } = props.domain;
|
|
311
|
+
const childZoneName = devDomain;
|
|
312
|
+
let childZone;
|
|
313
|
+
if (hostedZoneId !== undefined) {
|
|
314
|
+
// Adopt mode: import only — no zone resource, no delegation record, no NS
|
|
315
|
+
// export (the zone's NS was delegated when the account Domain deployed).
|
|
316
|
+
// Plain CDK import, NOT the HostedZone wrapper: its import path re-emits
|
|
317
|
+
// the global `<zone>-hosted-zone-id` export, colliding with the owning
|
|
318
|
+
// Domain stack's.
|
|
319
|
+
childZone = PublicHostedZone.fromHostedZoneAttributes(this, DEV_SUBSTRATE_ZONE_ID, { hostedZoneId, zoneName: childZoneName });
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
// Create mode: createDelegationRole:false because the PARENT account owns
|
|
323
|
+
// the delegation role (this is the child). The HostedZone wrapper's create
|
|
324
|
+
// path auto-emits the `<zone>-nameservers` CFN export the parent consumes
|
|
325
|
+
// to point NS records at us — no manual export needed.
|
|
326
|
+
const createdZone = new HostedZone(this, DEV_SUBSTRATE_ZONE_ID, {
|
|
327
|
+
zoneName: childZoneName,
|
|
328
|
+
createDelegationRole: false
|
|
334
329
|
});
|
|
330
|
+
childZone = createdZone.hostedZone;
|
|
331
|
+
// Case-1 same-org delegation fires only when a parent role ARN is supplied.
|
|
332
|
+
// The in-stack custom resource assumes the parent account's DelegationRole by
|
|
333
|
+
// LITERAL ARN (Role.fromRoleArn) — NOT Fn.importValue, which resolves
|
|
334
|
+
// same-account only and is exactly why the DomainDelegation pattern cannot go
|
|
335
|
+
// cross-account (design VD2-a). The parent DelegationRole trusts the whole
|
|
336
|
+
// org (OrganizationPrincipal), so a sibling dev account can assume it. Absent
|
|
337
|
+
// ARN ⇒ Case 2 (worker UPSERT, B3b) / Case 3 (manual), handled out of band:
|
|
338
|
+
// the zone + NS export above is all the substrate emits.
|
|
339
|
+
if (parentDelegationRoleArn !== undefined) {
|
|
340
|
+
// Re-checked here because direct construct calls bypass the Zod schema.
|
|
341
|
+
if (parentDomain === undefined) {
|
|
342
|
+
throw new Error(`DevSubstrate domain "${devDomain}": parentDelegationRoleArn was ` +
|
|
343
|
+
`supplied without parentDomain. Set domain.parentDomain to the ` +
|
|
344
|
+
`parent zone the DelegationRole belongs to (a proper DNS suffix ` +
|
|
345
|
+
`of devDomain), or omit parentDelegationRoleArn.`);
|
|
346
|
+
}
|
|
347
|
+
const parentRole = Role.fromRoleArn(this, DEV_SUBSTRATE_PARENT_ROLE_ID, parentDelegationRoleArn);
|
|
348
|
+
new CrossAccountDelegationRecord(this, DEV_SUBSTRATE_DELEGATION_ID, {
|
|
349
|
+
delegationRole: parentRole,
|
|
350
|
+
delegatedZone: childZone,
|
|
351
|
+
delegatedZoneName: childZoneName,
|
|
352
|
+
parentHostedZoneName: parentDomain
|
|
353
|
+
});
|
|
354
|
+
}
|
|
335
355
|
}
|
|
336
356
|
// Step 2 of the R2 two-step gate: synthesising the cert before the step-1 NS
|
|
337
357
|
// has propagated hangs ACM DNS-validation for hours, so "zone" stops here.
|
|
@@ -342,7 +362,7 @@ export class DevSubstrate extends Construct {
|
|
|
342
362
|
// would otherwise render an invalid CFN export name.
|
|
343
363
|
const certificate = new DomainCertificate(this, DEV_SUBSTRATE_CERT_ID, {
|
|
344
364
|
domainName: `*.${childZoneName}`,
|
|
345
|
-
hostedZone: childZone
|
|
365
|
+
hostedZone: childZone,
|
|
346
366
|
exportCertificateArn: false
|
|
347
367
|
});
|
|
348
368
|
// Unconditional 404 as on :80 (per-slot rules attach out of band, §5.4). open
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Alarm } from "aws-cdk-lib/aws-cloudwatch";
|
|
2
|
+
import type { ITopic } from "aws-cdk-lib/aws-sns";
|
|
3
|
+
import type { Construct } from "constructs";
|
|
4
|
+
export interface BuildkiteAlarmsProps {
|
|
5
|
+
scope: Construct;
|
|
6
|
+
buildkiteOrgSlug: string;
|
|
7
|
+
buildkiteQueue: string;
|
|
8
|
+
autoScalingGroupName: string;
|
|
9
|
+
/**
|
|
10
|
+
* Omitted → the alarms exist on the console but page nobody. Unlike the
|
|
11
|
+
* sibling builders the topic is optional: `App.addBuildkite` defaults it to
|
|
12
|
+
* the shared platform topic, and a bare construct must still synth.
|
|
13
|
+
*/
|
|
14
|
+
alarmTopic?: ITopic;
|
|
15
|
+
applicationId?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The fleet's two Phase-1 alarms (design § D14) — the "~zero babysitting"
|
|
19
|
+
* posture is honest only with these:
|
|
20
|
+
*
|
|
21
|
+
* 1. Scaler heartbeat — `ScheduledJobsCount` goes MISSING for 15 minutes.
|
|
22
|
+
* The scaler publishes every poll, so metric absence means the scaler
|
|
23
|
+
* Lambda is dead or failing; `treatMissingData: BREACHING` is the alarm's
|
|
24
|
+
* entire mechanism.
|
|
25
|
+
* 2. Queued with zero capacity — jobs scheduled while the ASG has no
|
|
26
|
+
* in-service instances for 15 minutes: the fleet cannot boot (AMI gone,
|
|
27
|
+
* quota, subnet failure) while work is waiting.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createBuildkiteAlarms(props: BuildkiteAlarmsProps): Alarm[];
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Duration } from "aws-cdk-lib";
|
|
2
|
+
import { Alarm, ComparisonOperator, MathExpression, Metric, TreatMissingData } from "aws-cdk-lib/aws-cloudwatch";
|
|
3
|
+
import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
|
|
4
|
+
import { buildAlarmDescription, registerAlarm, tagAlarmsWithApplicationId } from "./alarmDefaults.js";
|
|
5
|
+
/**
|
|
6
|
+
* Metrics namespace the buildkite-agent-scaler publishes to, dimensioned by
|
|
7
|
+
* {Org, Queue} — BOTH dimensions are required; querying Queue alone reads no
|
|
8
|
+
* data (CloudWatch dimension matching is exact-set), leaving the heartbeat
|
|
9
|
+
* permanently ALARM and the queued alarm permanently inert. Phase-1b rollout
|
|
10
|
+
* gate: verify both metrics carry data after the first build before trusting
|
|
11
|
+
* the alarms (design § D13/D14) — a namespace/dimension mismatch here is
|
|
12
|
+
* invisible to the synth tests.
|
|
13
|
+
*/
|
|
14
|
+
const SCALER_METRICS_NAMESPACE = "Buildkite";
|
|
15
|
+
/**
|
|
16
|
+
* The fleet's two Phase-1 alarms (design § D14) — the "~zero babysitting"
|
|
17
|
+
* posture is honest only with these:
|
|
18
|
+
*
|
|
19
|
+
* 1. Scaler heartbeat — `ScheduledJobsCount` goes MISSING for 15 minutes.
|
|
20
|
+
* The scaler publishes every poll, so metric absence means the scaler
|
|
21
|
+
* Lambda is dead or failing; `treatMissingData: BREACHING` is the alarm's
|
|
22
|
+
* entire mechanism.
|
|
23
|
+
* 2. Queued with zero capacity — jobs scheduled while the ASG has no
|
|
24
|
+
* in-service instances for 15 minutes: the fleet cannot boot (AMI gone,
|
|
25
|
+
* quota, subnet failure) while work is waiting.
|
|
26
|
+
*/
|
|
27
|
+
export function createBuildkiteAlarms(props) {
|
|
28
|
+
const { scope, buildkiteOrgSlug, buildkiteQueue, autoScalingGroupName, alarmTopic, applicationId } = props;
|
|
29
|
+
const alarms = [];
|
|
30
|
+
const snsAction = alarmTopic !== undefined ? new SnsAction(alarmTopic) : undefined;
|
|
31
|
+
const scheduledJobs = new Metric({
|
|
32
|
+
namespace: SCALER_METRICS_NAMESPACE,
|
|
33
|
+
metricName: "ScheduledJobsCount",
|
|
34
|
+
dimensionsMap: {
|
|
35
|
+
Org: buildkiteOrgSlug,
|
|
36
|
+
Queue: buildkiteQueue
|
|
37
|
+
},
|
|
38
|
+
statistic: "Maximum",
|
|
39
|
+
period: Duration.minutes(5)
|
|
40
|
+
});
|
|
41
|
+
const heartbeatAlarm = new Alarm(scope, "ScalerHeartbeatAlarm", {
|
|
42
|
+
alarmDescription: buildAlarmDescription(`Buildkite scaler for queue '${buildkiteQueue}' has stopped ` +
|
|
43
|
+
"publishing metrics — scaler Lambda dead or erroring. Jobs will " +
|
|
44
|
+
"queue with no scale-out.", applicationId),
|
|
45
|
+
metric: scheduledJobs,
|
|
46
|
+
comparisonOperator: ComparisonOperator.LESS_THAN_THRESHOLD,
|
|
47
|
+
threshold: 0,
|
|
48
|
+
evaluationPeriods: 3,
|
|
49
|
+
treatMissingData: TreatMissingData.BREACHING
|
|
50
|
+
});
|
|
51
|
+
if (snsAction !== undefined) {
|
|
52
|
+
registerAlarm(heartbeatAlarm, snsAction, alarms);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
alarms.push(heartbeatAlarm);
|
|
56
|
+
}
|
|
57
|
+
const inServiceInstances = new Metric({
|
|
58
|
+
namespace: "AWS/AutoScaling",
|
|
59
|
+
metricName: "GroupInServiceInstances",
|
|
60
|
+
dimensionsMap: { AutoScalingGroupName: autoScalingGroupName },
|
|
61
|
+
statistic: "Maximum",
|
|
62
|
+
period: Duration.minutes(5)
|
|
63
|
+
});
|
|
64
|
+
const queuedWithZeroCapacity = new MathExpression({
|
|
65
|
+
expression: "IF(scheduled > 0 AND inService == 0, 1, 0)",
|
|
66
|
+
usingMetrics: {
|
|
67
|
+
scheduled: scheduledJobs,
|
|
68
|
+
inService: inServiceInstances
|
|
69
|
+
},
|
|
70
|
+
period: Duration.minutes(5)
|
|
71
|
+
});
|
|
72
|
+
const queuedAlarm = new Alarm(scope, "QueuedWithZeroCapacityAlarm", {
|
|
73
|
+
alarmDescription: buildAlarmDescription(`Buildkite queue '${buildkiteQueue}' has scheduled jobs but zero ` +
|
|
74
|
+
"in-service agents for 15 minutes — the fleet cannot boot (AMI, " +
|
|
75
|
+
"quota, or subnet failure) while work waits.", applicationId),
|
|
76
|
+
metric: queuedWithZeroCapacity,
|
|
77
|
+
comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
|
|
78
|
+
threshold: 1,
|
|
79
|
+
evaluationPeriods: 3,
|
|
80
|
+
treatMissingData: TreatMissingData.NOT_BREACHING
|
|
81
|
+
});
|
|
82
|
+
if (snsAction !== undefined) {
|
|
83
|
+
registerAlarm(queuedAlarm, snsAction, alarms);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
alarms.push(queuedAlarm);
|
|
87
|
+
}
|
|
88
|
+
tagAlarmsWithApplicationId(alarms, applicationId);
|
|
89
|
+
return alarms;
|
|
90
|
+
}
|
|
@@ -4,5 +4,6 @@ export { createRdsAlarms, type RdsAlarmThresholds, type RdsAlarmsProps } from ".
|
|
|
4
4
|
export { createLambdaAlarms, type LambdaAlarmThresholds, type LambdaAlarmsProps } from "./lambdaAlarms.js";
|
|
5
5
|
export { createScheduleAlarms, type ScheduleAlarmThresholds, type CreateScheduleAlarmsProps } from "./scheduleAlarms.js";
|
|
6
6
|
export { createClickHouseAlarms, type ClickHouseAlarmThresholds, type ClickHouseAlarmsProps } from "./clickhouseAlarms.js";
|
|
7
|
+
export { createBuildkiteAlarms, type BuildkiteAlarmsProps } from "./buildkiteAlarms.js";
|
|
7
8
|
export { createLogPatternAlarms, type LogPatternAlarmSpec, type LogPatternAlarmsProps } from "./logPatternAlarms.js";
|
|
8
9
|
export { METRIC_NAMESPACE, type MetricNamespace } from "./metricNamespaces.js";
|
|
@@ -4,5 +4,6 @@ export { createRdsAlarms } from "./rdsAlarms.js";
|
|
|
4
4
|
export { createLambdaAlarms } from "./lambdaAlarms.js";
|
|
5
5
|
export { createScheduleAlarms } from "./scheduleAlarms.js";
|
|
6
6
|
export { createClickHouseAlarms } from "./clickhouseAlarms.js";
|
|
7
|
+
export { createBuildkiteAlarms } from "./buildkiteAlarms.js";
|
|
7
8
|
export { createLogPatternAlarms } from "./logPatternAlarms.js";
|
|
8
9
|
export { METRIC_NAMESPACE } from "./metricNamespaces.js";
|
|
@@ -13,6 +13,9 @@ export class HostedZoneFactory {
|
|
|
13
13
|
return new HostedZone(stack.getStack(), `${safeZone}HostedZone`, {
|
|
14
14
|
hostedZoneId,
|
|
15
15
|
zoneName,
|
|
16
|
+
// BYO app-stack imports never mint the role: its fixed RoleName would
|
|
17
|
+
// collide with the domain stack's own DelegationRole in the same account.
|
|
18
|
+
createDelegationRole: false,
|
|
16
19
|
costAllocationEnvironment: opts?.costAllocationEnvironment,
|
|
17
20
|
costAllocationDomain: opts?.costAllocationDomain,
|
|
18
21
|
description: opts?.description
|
|
@@ -56,25 +59,6 @@ export class HostedZone extends Construct {
|
|
|
56
59
|
value: Fn.join(",", created.hostedZoneNameServers ?? []),
|
|
57
60
|
exportName: exportNames.nameservers
|
|
58
61
|
});
|
|
59
|
-
// Org-gate: a single account has no OrganisationId export, so an org-trusting
|
|
60
|
-
// delegation role would leave an unresolved Fn::ImportValue and roll the stack
|
|
61
|
-
// back. Default follows org presence; explicit `true` opts in (and fails fast).
|
|
62
|
-
const inOrganisation = resolveOrgId(this.node) !== undefined;
|
|
63
|
-
if (props.createDelegationRole === true && !inOrganisation) {
|
|
64
|
-
throw new Error(`HostedZone "${props.zoneName}": createDelegationRole was requested but ` +
|
|
65
|
-
`this account is not part of an AWS Organization (no "orgId" context). ` +
|
|
66
|
-
`Cross-account DNS delegation requires an organisation — omit ` +
|
|
67
|
-
`createDelegationRole for single-account setups, or connect an organisation.`);
|
|
68
|
-
}
|
|
69
|
-
if (props.createDelegationRole ?? inOrganisation) {
|
|
70
|
-
this.delegationRole = new DelegationRole(this, `${safeZone}DelegationRole`, {
|
|
71
|
-
zoneName: props.zoneName,
|
|
72
|
-
hostedZone: created,
|
|
73
|
-
organisationIdExportName: props.organisationIdExportName,
|
|
74
|
-
costAllocationEnvironment: props.costAllocationEnvironment,
|
|
75
|
-
costAllocationDomain: props.costAllocationDomain
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
62
|
}
|
|
79
63
|
else {
|
|
80
64
|
// Import path — `fromHostedZoneAttributes` creates a reference, not a
|
|
@@ -88,6 +72,26 @@ export class HostedZone extends Construct {
|
|
|
88
72
|
this.isImported = true;
|
|
89
73
|
Tags.of(this).add("fjall:description", this.description);
|
|
90
74
|
}
|
|
75
|
+
// Org-gate: a single account has no OrganisationId export, so an org-trusting
|
|
76
|
+
// delegation role would leave an unresolved Fn::ImportValue and roll the stack
|
|
77
|
+
// back. Default follows org presence; explicit `true` opts in (and fails fast).
|
|
78
|
+
// Runs for created AND adopted zones — `grantDelegation` is on IHostedZone.
|
|
79
|
+
const inOrganisation = resolveOrgId(this.node) !== undefined;
|
|
80
|
+
if (props.createDelegationRole === true && !inOrganisation) {
|
|
81
|
+
throw new Error(`HostedZone "${props.zoneName}": createDelegationRole was requested but ` +
|
|
82
|
+
`this account is not part of an AWS Organization (no "orgId" context). ` +
|
|
83
|
+
`Cross-account DNS delegation requires an organisation — omit ` +
|
|
84
|
+
`createDelegationRole for single-account setups, or connect an organisation.`);
|
|
85
|
+
}
|
|
86
|
+
if (props.createDelegationRole ?? inOrganisation) {
|
|
87
|
+
this.delegationRole = new DelegationRole(this, `${safeZone}DelegationRole`, {
|
|
88
|
+
zoneName: props.zoneName,
|
|
89
|
+
hostedZone: this.hostedZone,
|
|
90
|
+
organisationIdExportName: props.organisationIdExportName,
|
|
91
|
+
costAllocationEnvironment: props.costAllocationEnvironment,
|
|
92
|
+
costAllocationDomain: props.costAllocationDomain
|
|
93
|
+
});
|
|
94
|
+
}
|
|
91
95
|
applyCostAllocationTags(this, {
|
|
92
96
|
service: "hostedZone",
|
|
93
97
|
domain: props.costAllocationDomain ?? props.zoneName,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/components-infrastructure",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@aws-sdk/client-organizations": "^3.1038.0",
|
|
70
|
-
"@fjall/generator": "^3.1
|
|
71
|
-
"@fjall/util": "^3.1
|
|
70
|
+
"@fjall/generator": "^3.2.1",
|
|
71
|
+
"@fjall/util": "^3.2.1",
|
|
72
72
|
"constructs": "^10.6.0"
|
|
73
73
|
},
|
|
74
74
|
"overrides": {
|
|
@@ -82,5 +82,5 @@
|
|
|
82
82
|
"engines": {
|
|
83
83
|
"node": ">=18.0.0"
|
|
84
84
|
},
|
|
85
|
-
"gitHead": "
|
|
85
|
+
"gitHead": "1a249298df7bf3104d9d49b47700234db815ebb1"
|
|
86
86
|
}
|