@fjall/components-infrastructure 2.34.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/app.d.ts +36 -2
- package/dist/lib/app.js +78 -3
- package/dist/lib/patterns/aws/apexDomainPattern.d.ts +11 -12
- package/dist/lib/patterns/aws/apexDomainPattern.js +11 -31
- package/dist/lib/patterns/aws/buildkite/alarms.d.ts +20 -0
- package/dist/lib/patterns/aws/buildkite/alarms.js +78 -0
- package/dist/lib/patterns/aws/buildkite/buildkite.d.ts +46 -0
- package/dist/lib/patterns/aws/buildkite/buildkite.js +233 -0
- package/dist/lib/patterns/aws/buildkite/iam.d.ts +35 -0
- package/dist/lib/patterns/aws/buildkite/iam.js +176 -0
- package/dist/lib/patterns/aws/buildkite/pins.d.ts +69 -0
- package/dist/lib/patterns/aws/buildkite/pins.js +80 -0
- package/dist/lib/patterns/aws/buildkite/schema.d.ts +80 -0
- package/dist/lib/patterns/aws/buildkite/schema.js +148 -0
- package/dist/lib/patterns/aws/buildkite/userData.d.ts +58 -0
- package/dist/lib/patterns/aws/buildkite/userData.js +226 -0
- package/dist/lib/patterns/aws/buildkite.d.ts +12 -59
- package/dist/lib/patterns/aws/buildkite.js +12 -310
- package/dist/lib/patterns/aws/cdn.js +25 -10
- package/dist/lib/patterns/aws/computeEcs.js +6 -1
- package/dist/lib/patterns/aws/computeEcsTypes.d.ts +8 -3
- package/dist/lib/patterns/aws/delegatedDomainPattern.d.ts +13 -4
- package/dist/lib/patterns/aws/delegatedDomainPattern.js +57 -8
- package/dist/lib/patterns/aws/devSubstrate.d.ts +2 -1
- package/dist/lib/patterns/aws/devSubstrate.js +18 -8
- package/dist/lib/patterns/aws/dnsRecordComposer.d.ts +2 -9
- package/dist/lib/patterns/aws/dnsRecordComposer.js +5 -97
- package/dist/lib/patterns/aws/domain.d.ts +3 -2
- package/dist/lib/patterns/aws/domain.js +3 -2
- package/dist/lib/patterns/aws/domainValidation.js +71 -46
- package/dist/lib/patterns/aws/externalRecordsPattern.js +2 -11
- package/dist/lib/patterns/aws/index.d.ts +1 -3
- package/dist/lib/patterns/aws/index.js +2 -3
- package/dist/lib/patterns/aws/interfaces/domain.d.ts +22 -8
- package/dist/lib/patterns/aws/interfaces/index.d.ts +0 -1
- package/dist/lib/patterns/aws/interfaces/index.js +3 -0
- package/dist/lib/patterns/aws/interfaces/pattern.d.ts +55 -10
- package/dist/lib/patterns/aws/patternDomain.d.ts +77 -0
- package/dist/lib/patterns/aws/patternDomain.js +198 -0
- package/dist/lib/patterns/aws/payload.js +23 -30
- package/dist/lib/patterns/aws/staticSite.d.ts +1 -0
- package/dist/lib/patterns/aws/staticSite.js +28 -33
- package/dist/lib/resources/aws/base/awsStack.d.ts +8 -0
- package/dist/lib/resources/aws/base/awsStack.js +20 -17
- package/dist/lib/resources/aws/compute/ec2.d.ts +15 -0
- package/dist/lib/resources/aws/compute/ec2.js +20 -1
- package/dist/lib/resources/aws/compute/ecs.d.ts +1 -1
- package/dist/lib/resources/aws/compute/ecs.js +1 -1
- package/dist/lib/resources/aws/compute/ecsNetworking.d.ts +1 -1
- package/dist/lib/resources/aws/compute/ecsNetworking.js +212 -47
- package/dist/lib/resources/aws/compute/ecsTypes.d.ts +60 -9
- package/dist/lib/resources/aws/compute/ecsValidation.d.ts +14 -1
- package/dist/lib/resources/aws/compute/ecsValidation.js +51 -0
- package/dist/lib/resources/aws/networking/dnsRecord/aRecord.js +3 -2
- package/dist/lib/resources/aws/networking/dnsRecord/aaaaRecord.js +3 -2
- package/dist/lib/resources/aws/networking/dnsRecord/aliasRecord.d.ts +23 -1
- package/dist/lib/resources/aws/networking/dnsRecord/aliasRecord.js +14 -3
- package/dist/lib/resources/aws/networking/dnsRecord/caaRecord.js +3 -2
- package/dist/lib/resources/aws/networking/dnsRecord/cnameRecord.js +3 -2
- package/dist/lib/resources/aws/networking/dnsRecord/dnsRecordBase.d.ts +10 -1
- package/dist/lib/resources/aws/networking/dnsRecord/dnsRecordBase.js +21 -4
- package/dist/lib/resources/aws/networking/dnsRecord/mxRecord.js +3 -2
- package/dist/lib/resources/aws/networking/dnsRecord/nsRecord.js +3 -2
- package/dist/lib/resources/aws/networking/dnsRecord/srvRecord.js +3 -2
- package/dist/lib/resources/aws/networking/dnsRecord/txtRecord.js +3 -2
- package/dist/lib/resources/aws/networking/domainCertificate.d.ts +28 -4
- package/dist/lib/resources/aws/networking/domainCertificate.js +24 -10
- package/dist/lib/resources/aws/networking/hostedZone.d.ts +11 -0
- package/dist/lib/resources/aws/networking/hostedZone.js +6 -0
- package/dist/lib/utils/costAllocationTags.d.ts +9 -0
- package/dist/lib/utils/costAllocationTags.js +6 -1
- package/dist/lib/utils/dnsRecordRegistry.d.ts +52 -0
- package/dist/lib/utils/dnsRecordRegistry.js +64 -0
- package/dist/lib/utils/domainTypes.d.ts +43 -36
- package/dist/lib/utils/domainTypes.js +65 -2
- package/dist/lib/utils/managedDomainContext.d.ts +37 -0
- package/dist/lib/utils/managedDomainContext.js +111 -0
- package/package.json +4 -4
package/dist/lib/app.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type CloudAssembly } from "aws-cdk-lib/cx-api";
|
|
|
3
3
|
import { type Construct } from "constructs";
|
|
4
4
|
import { type IVpc } from "aws-cdk-lib/aws-ec2";
|
|
5
5
|
import { type Role } from "aws-cdk-lib/aws-iam";
|
|
6
|
-
import { AwsStack } from "./resources/index.js";
|
|
6
|
+
import { AwsStack, type AwsStackProps } from "./resources/index.js";
|
|
7
7
|
import { type Ecr } from "./resources/aws/storage/ecr.js";
|
|
8
8
|
import { type AnyDatabase } from "./patterns/aws/database.js";
|
|
9
9
|
import { type INetworkProps, type Network } from "./patterns/aws/network.js";
|
|
@@ -16,6 +16,7 @@ import { type RemovalPolicyString } from "./resources/aws/messaging/utils.js";
|
|
|
16
16
|
import { type ServiceRegistrationProps } from "./resources/aws/networking/serviceDiscovery.js";
|
|
17
17
|
import { type IPrivateDnsNamespace, type IService } from "aws-cdk-lib/aws-servicediscovery";
|
|
18
18
|
import { type AnyCompute } from "./patterns/aws/compute.js";
|
|
19
|
+
import { Buildkite, type BuildkitePropsInput } from "./patterns/aws/buildkite.js";
|
|
19
20
|
import { DevSubstrate, type IDevSubstrateProps } from "./patterns/aws/devSubstrate.js";
|
|
20
21
|
import { type Storage, type StorageFactoryFn } from "./patterns/aws/storage.js";
|
|
21
22
|
import { type AnyPattern, type PatternFactoryFn } from "./patterns/aws/pattern.js";
|
|
@@ -123,8 +124,10 @@ export declare class App extends CdkApp {
|
|
|
123
124
|
*
|
|
124
125
|
* @param key - The key of the stack
|
|
125
126
|
* @param dependencies - The stack(s) that this stack depends on
|
|
127
|
+
* @param props - Stack props applied on first creation only (cached
|
|
128
|
+
* stacks are returned as-is)
|
|
126
129
|
*/
|
|
127
|
-
getStack(key: string, dependencies?: AwsStack | AwsStack[]): AwsStack;
|
|
130
|
+
getStack(key: string, dependencies?: AwsStack | AwsStack[], props?: AwsStackProps): AwsStack;
|
|
128
131
|
/**
|
|
129
132
|
* Retrieve default compute stack - named as `${this.name}Compute`
|
|
130
133
|
*
|
|
@@ -162,6 +165,21 @@ export declare class App extends CdkApp {
|
|
|
162
165
|
* require VPC, but we maintain consistent stack dependency patterns.
|
|
163
166
|
*/
|
|
164
167
|
getDefaultMessagingStack(): AwsStack;
|
|
168
|
+
/**
|
|
169
|
+
* Retrieve the app-owned us-east-1 certificate stack — named
|
|
170
|
+
* `${this.name}UsEast1Certificates` (domain gold-plating D3, BYO path).
|
|
171
|
+
*
|
|
172
|
+
* CloudFront accepts viewer certificates from us-east-1 only, so
|
|
173
|
+
* CloudFront-consuming patterns (staticsite, payload) whose app deploys to
|
|
174
|
+
* another region provision their DomainCertificate here; the distribution
|
|
175
|
+
* in the Cdn stack consumes the ARN through CDK cross-region references.
|
|
176
|
+
*
|
|
177
|
+
* `crossRegionReferences: true` must be set on BOTH the producing and the
|
|
178
|
+
* consuming stack; both get it at construction (the Cdn stack in
|
|
179
|
+
* getDefaultCdnStack, this stack here) and the emitted
|
|
180
|
+
* Custom::CrossRegionExportWriter/Reader pair is pinned by template test.
|
|
181
|
+
*/
|
|
182
|
+
getUsEast1CertificateStack(): AwsStack;
|
|
165
183
|
/**
|
|
166
184
|
* Get a VPC by name. If no name is provided, returns the default VPC.
|
|
167
185
|
*
|
|
@@ -260,6 +278,22 @@ export declare class App extends CdkApp {
|
|
|
260
278
|
* would synth and hash but never deploy.
|
|
261
279
|
*/
|
|
262
280
|
addDevSubstrate(props: IDevSubstrateProps): DevSubstrate;
|
|
281
|
+
/**
|
|
282
|
+
* Instantiate the self-hosted Buildkite agent fleet on the default compute
|
|
283
|
+
* stack (design `aiDocs/designs/2026-07-18-buildkite-selfhosted-agents.md`).
|
|
284
|
+
*
|
|
285
|
+
* Same placement contract as `addDevSubstrate` above: deploy-core selects
|
|
286
|
+
* stacks by the fixed six-category enum, so scoping under
|
|
287
|
+
* `${stackPrefix}Compute` is what makes the fleet actually deploy. Also the
|
|
288
|
+
* same registry posture — Fjall-managed platform infrastructure,
|
|
289
|
+
* deliberately NOT on the customer create/scaffold surface (design § D3).
|
|
290
|
+
*
|
|
291
|
+
* Cost-allocation environment resolves from `getConfig()` (design § D10) so
|
|
292
|
+
* the fleet's environment tag matches the deploying config rather than the
|
|
293
|
+
* construct's `management` fallback; an explicit
|
|
294
|
+
* `props.costAllocationEnvironment` still wins.
|
|
295
|
+
*/
|
|
296
|
+
addBuildkite(props: BuildkitePropsInput): Buildkite;
|
|
263
297
|
/**
|
|
264
298
|
* Add a database resource to the default database stack using the factory pattern.
|
|
265
299
|
* Returns the appropriate database type based on the factory used.
|
package/dist/lib/app.js
CHANGED
|
@@ -9,15 +9,18 @@ import { NetworkFactory } from "./patterns/aws/network.js";
|
|
|
9
9
|
import { MessagingFactory } from "./patterns/aws/messaging.js";
|
|
10
10
|
import { Schedule } from "./resources/aws/messaging/schedule.js";
|
|
11
11
|
import { ServiceDiscoveryNamespace } from "./resources/aws/networking/serviceDiscovery.js";
|
|
12
|
+
import { Buildkite } from "./patterns/aws/buildkite.js";
|
|
12
13
|
import { DevSubstrate } from "./patterns/aws/devSubstrate.js";
|
|
13
14
|
import { StandardTagsAspect } from "./utils/standardTagsAspect.js";
|
|
14
15
|
import { BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP } from "./utils/backupTierMapping.js";
|
|
15
16
|
import { randomBytes } from "crypto";
|
|
16
17
|
import { getConfig } from "./utils/getConfig.js";
|
|
18
|
+
import { UNKNOWN_ENVIRONMENT } from "./utils/env.js";
|
|
17
19
|
import AuditRoleFactory from "./resources/aws/audit/auditRole.js";
|
|
18
20
|
import { FJALL_AUDIT_CONFIG } from "./config/audit.js";
|
|
19
21
|
import { FjallLogger } from "./utils/validationLogger.js";
|
|
20
22
|
import { getManifestCollector, writeManifest } from "./utils/manifestWriter.js";
|
|
23
|
+
import { resetDnsRecordRegistry } from "./utils/dnsRecordRegistry.js";
|
|
21
24
|
import { toPascalCase, toKebab } from "./utils/capitaliseString.js";
|
|
22
25
|
import { COST_ALLOCATION_TAGS } from "./utils/costAllocationTags.js";
|
|
23
26
|
import { DEFAULT_ORG_ID, resolveOrgId } from "./utils/cdkContext.js";
|
|
@@ -168,6 +171,10 @@ export class App extends CdkApp {
|
|
|
168
171
|
}
|
|
169
172
|
static resetForTesting() {
|
|
170
173
|
App.instance = null;
|
|
174
|
+
// App-scoped synth state resets with the App singleton — the DNS
|
|
175
|
+
// record-collision registry (D5) follows the manifest-collector lifecycle
|
|
176
|
+
// without requiring a third per-test reset call.
|
|
177
|
+
resetDnsRecordRegistry();
|
|
171
178
|
}
|
|
172
179
|
/**
|
|
173
180
|
* Retrieve a stack by key. If the stack does not exist, it will be created.
|
|
@@ -175,8 +182,10 @@ export class App extends CdkApp {
|
|
|
175
182
|
*
|
|
176
183
|
* @param key - The key of the stack
|
|
177
184
|
* @param dependencies - The stack(s) that this stack depends on
|
|
185
|
+
* @param props - Stack props applied on first creation only (cached
|
|
186
|
+
* stacks are returned as-is)
|
|
178
187
|
*/
|
|
179
|
-
getStack(key, dependencies) {
|
|
188
|
+
getStack(key, dependencies, props) {
|
|
180
189
|
// Apply the aspect once before creating the first stack
|
|
181
190
|
if (!this.aspectApplied &&
|
|
182
191
|
Object.keys(this.stacks).length === 0 &&
|
|
@@ -184,7 +193,7 @@ export class App extends CdkApp {
|
|
|
184
193
|
this.applyTagsAspect();
|
|
185
194
|
}
|
|
186
195
|
if (!this.stacks[key]) {
|
|
187
|
-
this.stacks[key] = new AwsStack(key, dependencies);
|
|
196
|
+
this.stacks[key] = new AwsStack(key, dependencies, props);
|
|
188
197
|
}
|
|
189
198
|
return this.stacks[key];
|
|
190
199
|
}
|
|
@@ -225,7 +234,10 @@ export class App extends CdkApp {
|
|
|
225
234
|
* stack is synthesised.
|
|
226
235
|
*/
|
|
227
236
|
getDefaultCdnStack() {
|
|
228
|
-
|
|
237
|
+
// crossRegionReferences is enabled at creation so the Cdn stack can
|
|
238
|
+
// consume a us-east-1 certificate (domain gold-plating D3, BYO path).
|
|
239
|
+
// The flag is inert unless a cross-region reference is synthesised.
|
|
240
|
+
return this.getStack(`${this.stackPrefix}Cdn`, this.networkDisabled ? undefined : this.getDefaultNetworkStack(), { crossRegionReferences: true });
|
|
229
241
|
}
|
|
230
242
|
/**
|
|
231
243
|
* Retrieve default messaging stack - named as `${this.name}Messaging`
|
|
@@ -237,6 +249,39 @@ export class App extends CdkApp {
|
|
|
237
249
|
getDefaultMessagingStack() {
|
|
238
250
|
return this.getStack(`${this.stackPrefix}Messaging`, this.getDefaultNetworkStack());
|
|
239
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Retrieve the app-owned us-east-1 certificate stack — named
|
|
254
|
+
* `${this.name}UsEast1Certificates` (domain gold-plating D3, BYO path).
|
|
255
|
+
*
|
|
256
|
+
* CloudFront accepts viewer certificates from us-east-1 only, so
|
|
257
|
+
* CloudFront-consuming patterns (staticsite, payload) whose app deploys to
|
|
258
|
+
* another region provision their DomainCertificate here; the distribution
|
|
259
|
+
* in the Cdn stack consumes the ARN through CDK cross-region references.
|
|
260
|
+
*
|
|
261
|
+
* `crossRegionReferences: true` must be set on BOTH the producing and the
|
|
262
|
+
* consuming stack; both get it at construction (the Cdn stack in
|
|
263
|
+
* getDefaultCdnStack, this stack here) and the emitted
|
|
264
|
+
* Custom::CrossRegionExportWriter/Reader pair is pinned by template test.
|
|
265
|
+
*/
|
|
266
|
+
getUsEast1CertificateStack() {
|
|
267
|
+
const key = `${this.stackPrefix}UsEast1Certificates`;
|
|
268
|
+
if (!this.stacks[key]) {
|
|
269
|
+
const account = process.env.CDK_DEFAULT_ACCOUNT;
|
|
270
|
+
if (!account) {
|
|
271
|
+
throw new Error(`App '${this.name}': the us-east-1 certificate stack needs a ` +
|
|
272
|
+
"concrete account (got CDK_DEFAULT_ACCOUNT undefined). " +
|
|
273
|
+
"Cross-region references resolve only between env-pinned stacks " +
|
|
274
|
+
"— deploy through the Fjall CLI, or export CDK_DEFAULT_ACCOUNT " +
|
|
275
|
+
"and CDK_DEFAULT_REGION before synthesising.");
|
|
276
|
+
}
|
|
277
|
+
this.stacks[key] = new AwsStack(key, undefined, {
|
|
278
|
+
accountId: account,
|
|
279
|
+
region: "us-east-1",
|
|
280
|
+
crossRegionReferences: true
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return this.stacks[key];
|
|
284
|
+
}
|
|
240
285
|
/**
|
|
241
286
|
* Get a VPC by name. If no name is provided, returns the default VPC.
|
|
242
287
|
*
|
|
@@ -437,6 +482,36 @@ export class App extends CdkApp {
|
|
|
437
482
|
computeStack.addConstruct(substrate);
|
|
438
483
|
return substrate;
|
|
439
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* Instantiate the self-hosted Buildkite agent fleet on the default compute
|
|
487
|
+
* stack (design `aiDocs/designs/2026-07-18-buildkite-selfhosted-agents.md`).
|
|
488
|
+
*
|
|
489
|
+
* Same placement contract as `addDevSubstrate` above: deploy-core selects
|
|
490
|
+
* stacks by the fixed six-category enum, so scoping under
|
|
491
|
+
* `${stackPrefix}Compute` is what makes the fleet actually deploy. Also the
|
|
492
|
+
* same registry posture — Fjall-managed platform infrastructure,
|
|
493
|
+
* deliberately NOT on the customer create/scaffold surface (design § D3).
|
|
494
|
+
*
|
|
495
|
+
* Cost-allocation environment resolves from `getConfig()` (design § D10) so
|
|
496
|
+
* the fleet's environment tag matches the deploying config rather than the
|
|
497
|
+
* construct's `management` fallback; an explicit
|
|
498
|
+
* `props.costAllocationEnvironment` still wins.
|
|
499
|
+
*/
|
|
500
|
+
addBuildkite(props) {
|
|
501
|
+
const computeStack = this.getDefaultComputeStack();
|
|
502
|
+
const configEnvironment = getConfig().environment;
|
|
503
|
+
const constructProps = { ...props, vpc: this.getVpc() };
|
|
504
|
+
// Post-spread injection so a caller-materialised `costAllocationEnvironment:
|
|
505
|
+
// undefined` counts as absent rather than clobbering the resolved value.
|
|
506
|
+
if (constructProps.costAllocationEnvironment === undefined &&
|
|
507
|
+
configEnvironment !== "" &&
|
|
508
|
+
configEnvironment !== UNKNOWN_ENVIRONMENT) {
|
|
509
|
+
constructProps.costAllocationEnvironment = configEnvironment;
|
|
510
|
+
}
|
|
511
|
+
const fleet = new Buildkite(computeStack.getStack(), "Buildkite", constructProps);
|
|
512
|
+
computeStack.addConstruct(fleet);
|
|
513
|
+
return fleet;
|
|
514
|
+
}
|
|
440
515
|
/**
|
|
441
516
|
* Add a database resource to the default database stack using the factory pattern.
|
|
442
517
|
* Returns the appropriate database type based on the factory used.
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
import type { Construct } from "constructs";
|
|
2
2
|
import type { IHostedZone } from "aws-cdk-lib/aws-route53";
|
|
3
3
|
import type { ICertificate } from "aws-cdk-lib/aws-certificatemanager";
|
|
4
|
-
import type { ManualRecord, Route53ApexProps
|
|
4
|
+
import type { ManualRecord, Route53ApexProps } from "./interfaces/domain.js";
|
|
5
5
|
export interface ApexDomainPatternResult {
|
|
6
6
|
readonly hostedZone: IHostedZone;
|
|
7
7
|
readonly certificates: Map<string, ICertificate>;
|
|
8
8
|
readonly nameServers: string[] | undefined;
|
|
9
9
|
readonly manualRecords: ManualRecord[];
|
|
10
|
-
readonly delegations: SubdomainDelegation[];
|
|
11
10
|
}
|
|
12
11
|
/**
|
|
13
12
|
* Composition for `registrar: "route53"`. Creates (or imports) the apex
|
|
14
|
-
* `HostedZone
|
|
15
|
-
* each child's nameservers published as exports), and composes all user
|
|
16
|
-
* records + certificates.
|
|
13
|
+
* `HostedZone` and composes all user records + certificates.
|
|
17
14
|
*
|
|
18
|
-
* Delegation
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* parent
|
|
24
|
-
*
|
|
15
|
+
* Delegation direction (D8): child writes NS. The apex zone's contribution
|
|
16
|
+
* to delegation is the org-gated `DelegationRole` the `HostedZone` wrapper
|
|
17
|
+
* creates on the create path — the delegated child `Domain` assumes that
|
|
18
|
+
* role by LITERAL ARN (`parentDelegationRoleArn`) and UPSERTs its own NS
|
|
19
|
+
* records into this zone via `CrossAccountZoneDelegationRecord`. The old
|
|
20
|
+
* parent-writes `delegations[]` path (NS records from `Fn.importValue` of
|
|
21
|
+
* child nameserver exports) was removed with the H13 legacy layer: it was
|
|
22
|
+
* export-locked, child-first-deploy-ordered, and removal required a parent
|
|
23
|
+
* redeploy.
|
|
25
24
|
*/
|
|
26
25
|
export declare function composeApexDomain(scope: Construct, props: Route53ApexProps): ApexDomainPatternResult;
|
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
import { Fn } from "aws-cdk-lib";
|
|
2
|
-
import { getDomainExportNames } from "@fjall/util";
|
|
3
1
|
import { HostedZone } from "../../resources/aws/networking/hostedZone.js";
|
|
4
2
|
import { DomainCertificate } from "../../resources/aws/networking/domainCertificate.js";
|
|
5
|
-
import { NsRecord } from "../../resources/aws/networking/dnsRecord/index.js";
|
|
6
3
|
import { composeTypedDnsRecords } from "./dnsRecordComposer.js";
|
|
7
4
|
import { toPascalCase, getSafeZoneName } from "../../utils/capitaliseString.js";
|
|
8
5
|
import { resolveOrgId } from "../../utils/cdkContext.js";
|
|
9
6
|
/**
|
|
10
7
|
* Composition for `registrar: "route53"`. Creates (or imports) the apex
|
|
11
|
-
* `HostedZone
|
|
12
|
-
* each child's nameservers published as exports), and composes all user
|
|
13
|
-
* records + certificates.
|
|
8
|
+
* `HostedZone` and composes all user records + certificates.
|
|
14
9
|
*
|
|
15
|
-
* Delegation
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* parent
|
|
21
|
-
*
|
|
10
|
+
* Delegation direction (D8): child writes NS. The apex zone's contribution
|
|
11
|
+
* to delegation is the org-gated `DelegationRole` the `HostedZone` wrapper
|
|
12
|
+
* creates on the create path — the delegated child `Domain` assumes that
|
|
13
|
+
* role by LITERAL ARN (`parentDelegationRoleArn`) and UPSERTs its own NS
|
|
14
|
+
* records into this zone via `CrossAccountZoneDelegationRecord`. The old
|
|
15
|
+
* parent-writes `delegations[]` path (NS records from `Fn.importValue` of
|
|
16
|
+
* child nameserver exports) was removed with the H13 legacy layer: it was
|
|
17
|
+
* export-locked, child-first-deploy-ordered, and removal required a parent
|
|
18
|
+
* redeploy.
|
|
22
19
|
*/
|
|
23
20
|
export function composeApexDomain(scope, props) {
|
|
24
21
|
const safeZone = toPascalCase(getSafeZoneName(props.zoneName));
|
|
@@ -51,22 +48,6 @@ export function composeApexDomain(scope, props) {
|
|
|
51
48
|
if (props.records && props.records.length > 0) {
|
|
52
49
|
composeTypedDnsRecords(scope, hostedZoneConstruct.hostedZone, props.zoneName, props.records);
|
|
53
50
|
}
|
|
54
|
-
const delegations = props.delegations ?? [];
|
|
55
|
-
delegations.forEach((delegation, index) => {
|
|
56
|
-
const childZoneName = `${delegation.subdomain}.${props.zoneName}`;
|
|
57
|
-
const safeChild = toPascalCase(getSafeZoneName(childZoneName));
|
|
58
|
-
const childExports = getDomainExportNames(childZoneName);
|
|
59
|
-
// Cross-phase dependency: child stack must deploy first; CFN fails here
|
|
60
|
-
// at deploy if the nameservers export does not yet exist.
|
|
61
|
-
const nameserversToken = Fn.importValue(childExports.nameservers);
|
|
62
|
-
new NsRecord(scope, `${safeZone}Delegation${safeChild}${index}`, {
|
|
63
|
-
zone: hostedZoneConstruct.hostedZone,
|
|
64
|
-
zoneName: props.zoneName,
|
|
65
|
-
recordName: delegation.subdomain,
|
|
66
|
-
// Fn.split returns a list token suitable for a multi-value NS record.
|
|
67
|
-
values: Fn.split(",", nameserversToken)
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
51
|
return {
|
|
71
52
|
hostedZone: hostedZoneConstruct.hostedZone,
|
|
72
53
|
certificates,
|
|
@@ -75,8 +56,7 @@ export function composeApexDomain(scope, props) {
|
|
|
75
56
|
nameServers: hostedZoneConstruct.isImported
|
|
76
57
|
? undefined
|
|
77
58
|
: (hostedZoneConstruct.nameServers ?? undefined),
|
|
78
|
-
manualRecords: []
|
|
79
|
-
delegations
|
|
59
|
+
manualRecords: []
|
|
80
60
|
};
|
|
81
61
|
}
|
|
82
62
|
function normaliseCertificate(cert) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Construct } from "constructs";
|
|
2
|
+
export interface BuildkiteAlarmParams {
|
|
3
|
+
readonly buildkiteOrgSlug: string;
|
|
4
|
+
readonly buildkiteQueue: string;
|
|
5
|
+
readonly autoScalingGroupName: string;
|
|
6
|
+
readonly alarmSnsTopicArn?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The fleet's two Phase-1 alarms (design § D14) — the "~zero babysitting"
|
|
10
|
+
* posture is honest only with these:
|
|
11
|
+
*
|
|
12
|
+
* 1. Scaler heartbeat — `ScheduledJobsCount` goes MISSING for 15 minutes.
|
|
13
|
+
* The scaler publishes every poll, so metric absence means the scaler
|
|
14
|
+
* Lambda is dead or failing; `treatMissingData: BREACHING` is the alarm's
|
|
15
|
+
* entire mechanism.
|
|
16
|
+
* 2. Queued with zero capacity — jobs scheduled while the ASG has no
|
|
17
|
+
* in-service instances for 15 minutes: the fleet cannot boot (AMI gone,
|
|
18
|
+
* quota, subnet failure) while work is waiting.
|
|
19
|
+
*/
|
|
20
|
+
export declare function addBuildkiteAlarms(scope: Construct, params: BuildkiteAlarmParams): void;
|
|
@@ -0,0 +1,78 @@
|
|
|
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 { Topic } from "aws-cdk-lib/aws-sns";
|
|
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 addBuildkiteAlarms(scope, params) {
|
|
28
|
+
const scheduledJobs = new Metric({
|
|
29
|
+
namespace: SCALER_METRICS_NAMESPACE,
|
|
30
|
+
metricName: "ScheduledJobsCount",
|
|
31
|
+
dimensionsMap: {
|
|
32
|
+
Org: params.buildkiteOrgSlug,
|
|
33
|
+
Queue: params.buildkiteQueue
|
|
34
|
+
},
|
|
35
|
+
statistic: "Maximum",
|
|
36
|
+
period: Duration.minutes(5)
|
|
37
|
+
});
|
|
38
|
+
const heartbeatAlarm = new Alarm(scope, "ScalerHeartbeatAlarm", {
|
|
39
|
+
alarmDescription: `Buildkite scaler for queue '${params.buildkiteQueue}' has stopped ` +
|
|
40
|
+
"publishing metrics — scaler Lambda dead or erroring. Jobs will queue " +
|
|
41
|
+
"with no scale-out.",
|
|
42
|
+
metric: scheduledJobs,
|
|
43
|
+
comparisonOperator: ComparisonOperator.LESS_THAN_THRESHOLD,
|
|
44
|
+
threshold: 0,
|
|
45
|
+
evaluationPeriods: 3,
|
|
46
|
+
treatMissingData: TreatMissingData.BREACHING
|
|
47
|
+
});
|
|
48
|
+
const inServiceInstances = new Metric({
|
|
49
|
+
namespace: "AWS/AutoScaling",
|
|
50
|
+
metricName: "GroupInServiceInstances",
|
|
51
|
+
dimensionsMap: { AutoScalingGroupName: params.autoScalingGroupName },
|
|
52
|
+
statistic: "Maximum",
|
|
53
|
+
period: Duration.minutes(5)
|
|
54
|
+
});
|
|
55
|
+
const queuedWithZeroCapacity = new MathExpression({
|
|
56
|
+
expression: "IF(scheduled > 0 AND inService == 0, 1, 0)",
|
|
57
|
+
usingMetrics: {
|
|
58
|
+
scheduled: scheduledJobs,
|
|
59
|
+
inService: inServiceInstances
|
|
60
|
+
},
|
|
61
|
+
period: Duration.minutes(5)
|
|
62
|
+
});
|
|
63
|
+
const queuedAlarm = new Alarm(scope, "QueuedWithZeroCapacityAlarm", {
|
|
64
|
+
alarmDescription: `Buildkite queue '${params.buildkiteQueue}' has scheduled jobs but ` +
|
|
65
|
+
"zero in-service agents for 15 minutes — the fleet cannot boot " +
|
|
66
|
+
"(AMI, quota, or subnet failure) while work waits.",
|
|
67
|
+
metric: queuedWithZeroCapacity,
|
|
68
|
+
comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
|
|
69
|
+
threshold: 1,
|
|
70
|
+
evaluationPeriods: 3,
|
|
71
|
+
treatMissingData: TreatMissingData.NOT_BREACHING
|
|
72
|
+
});
|
|
73
|
+
if (params.alarmSnsTopicArn !== undefined) {
|
|
74
|
+
const topic = Topic.fromTopicArn(scope, "BuildkiteAlarmTopic", params.alarmSnsTopicArn);
|
|
75
|
+
heartbeatAlarm.addAlarmAction(new SnsAction(topic));
|
|
76
|
+
queuedAlarm.addAlarmAction(new SnsAction(topic));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type IVpc } from "aws-cdk-lib/aws-ec2";
|
|
2
|
+
import { Construct } from "constructs";
|
|
3
|
+
import { type BuildkitePropsInput } from "./schema.js";
|
|
4
|
+
export type BuildkiteConstructProps = BuildkitePropsInput & {
|
|
5
|
+
readonly vpc: IVpc;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Self-hosted Buildkite agent fleet on the pinned Elastic CI Stack AMIs —
|
|
9
|
+
* scale-to-zero ASG + buildkite-agent-scaler, no secret ever transiting
|
|
10
|
+
* synth, allowlist-only instance IAM. Design:
|
|
11
|
+
* `aiDocs/designs/2026-07-18-buildkite-selfhosted-agents.md`.
|
|
12
|
+
*
|
|
13
|
+
* A Construct, not a Stack: instantiate via `App.addBuildkite(props)`, which
|
|
14
|
+
* scopes it under the default compute stack so deploy-core's fixed
|
|
15
|
+
* six-category stack selection actually deploys it (the DevSubstrate
|
|
16
|
+
* placement precedent). Network posture is public-subnet + public-IP +
|
|
17
|
+
* zero-ingress (design § D5): `associatePublicIpAddress` MUST be explicit —
|
|
18
|
+
* the wrapper's `!!keyPair` default silently produced no-egress agents in
|
|
19
|
+
* public subnets pre-refactor.
|
|
20
|
+
*/
|
|
21
|
+
export declare class Buildkite extends Construct {
|
|
22
|
+
readonly artifactBucketName: string;
|
|
23
|
+
readonly secretsBucketName: string;
|
|
24
|
+
readonly autoScalingGroupName: string;
|
|
25
|
+
constructor(scope: Construct, id: string, props: BuildkiteConstructProps);
|
|
26
|
+
/**
|
|
27
|
+
* Ship the per-job `env` hook into the managed secrets bucket. The
|
|
28
|
+
* s3-secrets-hooks plugin sources this file at the start of EVERY job, so
|
|
29
|
+
* FJALL_API_KEY is read fresh from SSM per job — rotation takes effect on
|
|
30
|
+
* the next job with no instance replacement (design § D4(iii)). The script
|
|
31
|
+
* is configuration, not a secret: shipping it through a CDK asset is fine;
|
|
32
|
+
* the secret VALUE only ever moves SSM → instance at job runtime.
|
|
33
|
+
*/
|
|
34
|
+
private addFjallApiKeyEnvHook;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Object key the s3-secrets-hooks plugin sources at the start of every job —
|
|
38
|
+
* upstream contract: `env` at the secrets-bucket root.
|
|
39
|
+
*/
|
|
40
|
+
export declare const FJALL_ENV_HOOK_OBJECT_KEY = "env";
|
|
41
|
+
/**
|
|
42
|
+
* The per-job env hook's script content (design § D4(iii)): FJALL_API_KEY is
|
|
43
|
+
* read fresh from SSM per job, so rotation takes effect on the next job with
|
|
44
|
+
* no instance replacement. Pure — the unit test pins the exact content.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildFjallApiKeyEnvHookScript(parameterName: string, region: string): string;
|