@fjall/components-infrastructure 28.4.0 → 30.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.
- package/dist/lib/app.d.ts +19 -6
- package/dist/lib/app.js +30 -10
- package/dist/lib/patterns/aws/buildkite/buildkite.d.ts +6 -0
- package/dist/lib/patterns/aws/buildkite/buildkite.js +8 -4
- package/dist/lib/patterns/aws/buildkite/factory.d.ts +3 -3
- package/dist/lib/patterns/aws/buildkite/factory.js +7 -4
- package/dist/lib/patterns/aws/interfaces/pattern.d.ts +7 -4
- package/dist/lib/patterns/aws/payload.js +6 -1
- package/dist/lib/patterns/aws/storage.d.ts +15 -0
- package/dist/lib/patterns/aws/storage.js +7 -4
- package/dist/lib/resources/aws/storage/s3.d.ts +14 -0
- package/dist/lib/resources/aws/storage/s3.js +7 -5
- package/dist/lib/utils/backupTierMapping.d.ts +11 -2
- package/dist/lib/utils/backupTierMapping.js +15 -2
- package/dist/lib/utils/standardTagsAspect.d.ts +7 -4
- package/dist/lib/utils/standardTagsAspect.js +9 -6
- package/package.json +3 -3
package/dist/lib/app.d.ts
CHANGED
|
@@ -55,7 +55,10 @@ export interface IAppOptions {
|
|
|
55
55
|
/**
|
|
56
56
|
* Backup configuration for automatic AWS Backup enrolment.
|
|
57
57
|
* - Object with tier: Tags all resources with `fjall:disasterRecovery:tier`
|
|
58
|
-
*
|
|
58
|
+
* and sets the app backup tier that resource defaults derive from — S3
|
|
59
|
+
* buckets version by default at `resilient`/`enterprise`; a per-bucket
|
|
60
|
+
* `backupVaultTier` overrides the tier for that bucket.
|
|
61
|
+
* - false: Explicitly disabled (no backup tag, no tier-derived defaults)
|
|
59
62
|
*/
|
|
60
63
|
backup?: {
|
|
61
64
|
tier: BackupTier;
|
|
@@ -107,14 +110,24 @@ export declare class App extends CdkApp {
|
|
|
107
110
|
private bastion?;
|
|
108
111
|
private networkDisabled;
|
|
109
112
|
private globalTags;
|
|
113
|
+
private backupTier?;
|
|
114
|
+
private backupTierObserved;
|
|
110
115
|
private aspectApplied;
|
|
111
116
|
private resourceInventory?;
|
|
112
117
|
private manifestCollector;
|
|
113
118
|
private constructor();
|
|
114
|
-
private
|
|
119
|
+
private applyBackupTier;
|
|
115
120
|
private initialiseTunnel;
|
|
116
121
|
private initialiseNetwork;
|
|
117
122
|
getName(): string;
|
|
123
|
+
/**
|
|
124
|
+
* The app-level backup tier from `backup: { tier }`, or undefined when no
|
|
125
|
+
* backup is declared. Factories thread it to resources whose defaults derive
|
|
126
|
+
* from it (S3 versioning); a per-resource `backupVaultTier` overrides it.
|
|
127
|
+
* Reading it pins the tier: a `backup` applied on a later `getApp` call
|
|
128
|
+
* throws, so no resource can carry the tier's tag without its defaults.
|
|
129
|
+
*/
|
|
130
|
+
getBackupTier(): BackupTier | undefined;
|
|
118
131
|
static getApp(name?: string, options?: IAppOptions): App;
|
|
119
132
|
static getInstance(name?: string, options?: IAppOptions): App;
|
|
120
133
|
/**
|
|
@@ -345,10 +358,10 @@ export declare class App extends CdkApp {
|
|
|
345
358
|
* Add a storage resource (S3) to the default storage stack using the factory pattern.
|
|
346
359
|
*
|
|
347
360
|
* @example
|
|
348
|
-
* // Private bucket (default)
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
361
|
+
* // Private bucket (default). Versioning follows the app backup tier
|
|
362
|
+
* // (`backup: { tier }` — on at resilient/enterprise); `versioned` or a
|
|
363
|
+
* // per-bucket `backupVaultTier` overrides it.
|
|
364
|
+
* const assets = app.addStorage(StorageFactory.build("Assets"));
|
|
352
365
|
*
|
|
353
366
|
* @example
|
|
354
367
|
* // Website bucket
|
package/dist/lib/app.js
CHANGED
|
@@ -48,6 +48,8 @@ export class App extends CdkApp {
|
|
|
48
48
|
bastion;
|
|
49
49
|
networkDisabled = false;
|
|
50
50
|
globalTags = {};
|
|
51
|
+
backupTier;
|
|
52
|
+
backupTierObserved = false;
|
|
51
53
|
aspectApplied = false;
|
|
52
54
|
resourceInventory;
|
|
53
55
|
manifestCollector;
|
|
@@ -64,9 +66,9 @@ export class App extends CdkApp {
|
|
|
64
66
|
// Initialise manifest collector for build-time service discovery
|
|
65
67
|
this.manifestCollector = getManifestCollector(this.name);
|
|
66
68
|
this.initialiseStandardTags();
|
|
67
|
-
// Apply backup tier
|
|
69
|
+
// Apply backup tier if configured
|
|
68
70
|
if (options?.backup && typeof options.backup === "object") {
|
|
69
|
-
this.
|
|
71
|
+
this.applyBackupTier(options.backup.tier);
|
|
70
72
|
}
|
|
71
73
|
// Initialise network immediately if configured
|
|
72
74
|
if (options?.network === false) {
|
|
@@ -83,7 +85,14 @@ export class App extends CdkApp {
|
|
|
83
85
|
this.eventBusOverride = options.eventBus;
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
|
-
|
|
88
|
+
applyBackupTier(tier) {
|
|
89
|
+
if (this.backupTierObserved) {
|
|
90
|
+
throw new Error(`Backup tier "${tier}" applied after a resource already resolved the app's tier: ` +
|
|
91
|
+
`that resource would carry the tier's disaster-recovery tag without its versioning default. ` +
|
|
92
|
+
`Pass \`backup\` on the first App.getApp("${this.name}") call, before addStorage(), ` +
|
|
93
|
+
`addBuildkite() or a Payload pattern.`);
|
|
94
|
+
}
|
|
95
|
+
this.backupTier = tier;
|
|
87
96
|
this.globalTags[BACKUP_TIER_TAG_KEY] = BACKUP_TIER_TAG_MAP[tier];
|
|
88
97
|
}
|
|
89
98
|
initialiseTunnel(config) {
|
|
@@ -109,6 +118,17 @@ export class App extends CdkApp {
|
|
|
109
118
|
getName() {
|
|
110
119
|
return this.name;
|
|
111
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* The app-level backup tier from `backup: { tier }`, or undefined when no
|
|
123
|
+
* backup is declared. Factories thread it to resources whose defaults derive
|
|
124
|
+
* from it (S3 versioning); a per-resource `backupVaultTier` overrides it.
|
|
125
|
+
* Reading it pins the tier: a `backup` applied on a later `getApp` call
|
|
126
|
+
* throws, so no resource can carry the tier's tag without its defaults.
|
|
127
|
+
*/
|
|
128
|
+
getBackupTier() {
|
|
129
|
+
this.backupTierObserved = true;
|
|
130
|
+
return this.backupTier;
|
|
131
|
+
}
|
|
112
132
|
static getApp(name, options) {
|
|
113
133
|
return App.getInstance(name, options);
|
|
114
134
|
}
|
|
@@ -145,11 +165,11 @@ export class App extends CdkApp {
|
|
|
145
165
|
!App.instance.networkDisabled) {
|
|
146
166
|
App.instance.initialiseNetwork(options.network);
|
|
147
167
|
}
|
|
148
|
-
// Apply backup
|
|
168
|
+
// Apply backup tier if configured and not already set
|
|
149
169
|
if (options?.backup &&
|
|
150
170
|
typeof options.backup === "object" &&
|
|
151
|
-
|
|
152
|
-
App.instance.
|
|
171
|
+
App.instance.backupTier === undefined) {
|
|
172
|
+
App.instance.applyBackupTier(options.backup.tier);
|
|
153
173
|
}
|
|
154
174
|
// Initialise tunnel if configured and not already created
|
|
155
175
|
if (options?.tunnel && !App.instance.bastion) {
|
|
@@ -557,10 +577,10 @@ export class App extends CdkApp {
|
|
|
557
577
|
* Add a storage resource (S3) to the default storage stack using the factory pattern.
|
|
558
578
|
*
|
|
559
579
|
* @example
|
|
560
|
-
* // Private bucket (default)
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
580
|
+
* // Private bucket (default). Versioning follows the app backup tier
|
|
581
|
+
* // (`backup: { tier }` — on at resilient/enterprise); `versioned` or a
|
|
582
|
+
* // per-bucket `backupVaultTier` overrides it.
|
|
583
|
+
* const assets = app.addStorage(StorageFactory.build("Assets"));
|
|
564
584
|
*
|
|
565
585
|
* @example
|
|
566
586
|
* // Website bucket
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { type IVpc } from "aws-cdk-lib/aws-ec2";
|
|
2
2
|
import { Construct } from "constructs";
|
|
3
|
+
import { type BackupTier } from "../../../utils/backupTierMapping.js";
|
|
3
4
|
import { type BuildkitePropsInput } from "./schema.js";
|
|
4
5
|
export type BuildkiteConstructProps = BuildkitePropsInput & {
|
|
5
6
|
readonly vpc: IVpc;
|
|
7
|
+
/**
|
|
8
|
+
* The owning app's backup tier — `BuildkiteFactory` fills it from the App
|
|
9
|
+
* so the artefact bucket's versioning default follows the tier.
|
|
10
|
+
*/
|
|
11
|
+
readonly appBackupTier?: BackupTier;
|
|
6
12
|
};
|
|
7
13
|
/**
|
|
8
14
|
* Self-hosted Buildkite agent fleet on the pinned Elastic CI Stack AMIs —
|
|
@@ -37,7 +37,7 @@ export class Buildkite extends Construct {
|
|
|
37
37
|
autoScalingGroupName;
|
|
38
38
|
constructor(scope, id, props) {
|
|
39
39
|
super(scope, id);
|
|
40
|
-
const { vpc, ...plainProps } = props;
|
|
40
|
+
const { vpc, appBackupTier, ...plainProps } = props;
|
|
41
41
|
const config = validateBuildkiteProps(plainProps);
|
|
42
42
|
// Deliberate asymmetry: the agent token is load-bearing (boot + scaler),
|
|
43
43
|
// so underivable throws; the api-key hook and per-pipeline job secrets
|
|
@@ -88,10 +88,14 @@ export class Buildkite extends Construct {
|
|
|
88
88
|
owner: config.costAllocationOwner
|
|
89
89
|
})
|
|
90
90
|
});
|
|
91
|
-
|
|
91
|
+
// Artefacts are write-once keys, so versioning follows the app backup tier
|
|
92
|
+
// at negligible cost; the bucket is otherwise scratch output and keeps the
|
|
93
|
+
// wrapper's env-aware default (DESTROY + pre-empty off prod).
|
|
94
|
+
const artifactBucket = new S3Bucket(this, `${id}ArtifactBucket`, {
|
|
95
|
+
appBackupTier
|
|
96
|
+
});
|
|
92
97
|
// Secrets (SSH deploy keys, git credentials) must survive stack teardown
|
|
93
|
-
// and accidental overwrite
|
|
94
|
-
// keeps the wrapper's env-aware default (DESTROY + pre-empty off prod).
|
|
98
|
+
// and accidental overwrite.
|
|
95
99
|
const managedSecretsBucket = new S3Bucket(this, `${id}ManagedSecretsBucket`, { versioned: true, removalPolicy: RemovalPolicy.RETAIN });
|
|
96
100
|
if (fjallApiKeyParameterName !== undefined ||
|
|
97
101
|
pipelineSecretsPrefix !== undefined) {
|
|
@@ -10,10 +10,10 @@ import type { BuildkitePropsInput } from "./schema.js";
|
|
|
10
10
|
*/
|
|
11
11
|
export type BuildkiteFactoryFn = (app: App, scope: Construct) => Buildkite;
|
|
12
12
|
/**
|
|
13
|
-
* Resolve construct props from plain-data input: VPC injection plus the
|
|
13
|
+
* Resolve construct props from plain-data input: VPC injection plus the
|
|
14
14
|
* app-level defaults (cost-allocation environment, shared alarm topic,
|
|
15
|
-
* applicationId). Single source for both `App.addBuildkite`
|
|
16
|
-
* (design § D2) — explicit props always win.
|
|
15
|
+
* applicationId, backup tier). Single source for both `App.addBuildkite`
|
|
16
|
+
* entry paths (design § D2) — explicit props always win.
|
|
17
17
|
*/
|
|
18
18
|
export declare function resolveBuildkiteConstructProps(app: App, props: BuildkitePropsInput): BuildkiteConstructProps;
|
|
19
19
|
/**
|
|
@@ -3,10 +3,10 @@ import { UNKNOWN_ENVIRONMENT } from "../../../utils/env.js";
|
|
|
3
3
|
import { getConfig } from "../../../utils/getConfig.js";
|
|
4
4
|
import { Buildkite } from "./buildkite.js";
|
|
5
5
|
/**
|
|
6
|
-
* Resolve construct props from plain-data input: VPC injection plus the
|
|
6
|
+
* Resolve construct props from plain-data input: VPC injection plus the
|
|
7
7
|
* app-level defaults (cost-allocation environment, shared alarm topic,
|
|
8
|
-
* applicationId). Single source for both `App.addBuildkite`
|
|
9
|
-
* (design § D2) — explicit props always win.
|
|
8
|
+
* applicationId, backup tier). Single source for both `App.addBuildkite`
|
|
9
|
+
* entry paths (design § D2) — explicit props always win.
|
|
10
10
|
*/
|
|
11
11
|
export function resolveBuildkiteConstructProps(app, props) {
|
|
12
12
|
const configEnvironment = getConfig().environment;
|
|
@@ -24,7 +24,10 @@ export function resolveBuildkiteConstructProps(app, props) {
|
|
|
24
24
|
if (constructProps.applicationId === undefined) {
|
|
25
25
|
constructProps.applicationId = app.getName();
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
const appBackupTier = app.getBackupTier();
|
|
28
|
+
return appBackupTier === undefined
|
|
29
|
+
? constructProps
|
|
30
|
+
: { ...constructProps, appBackupTier };
|
|
28
31
|
}
|
|
29
32
|
/**
|
|
30
33
|
* Factory for the self-hosted Buildkite agent fleet — the codemod add path
|
|
@@ -201,7 +201,12 @@ export interface PayloadComputeConfig {
|
|
|
201
201
|
* S3 bucket configuration for pattern sub-buckets.
|
|
202
202
|
*/
|
|
203
203
|
export interface PatternStorageBucketConfig {
|
|
204
|
-
/**
|
|
204
|
+
/**
|
|
205
|
+
* Bucket versioning. Absent, the pattern decides: the media bucket follows
|
|
206
|
+
* the app backup tier (on at `resilient`/`enterprise`), while assets (build
|
|
207
|
+
* output, pruned per deploy) and cache (ISR, regenerated at runtime) stay
|
|
208
|
+
* off at every tier. An explicit value always wins.
|
|
209
|
+
*/
|
|
205
210
|
versioned?: boolean;
|
|
206
211
|
}
|
|
207
212
|
/**
|
|
@@ -210,9 +215,7 @@ export interface PatternStorageBucketConfig {
|
|
|
210
215
|
*
|
|
211
216
|
* @example
|
|
212
217
|
* storage: {
|
|
213
|
-
*
|
|
214
|
-
* cache: { versioned: false },
|
|
215
|
-
* media: { versioned: true }
|
|
218
|
+
* media: { versioned: true } // force on below the resilient tier
|
|
216
219
|
* }
|
|
217
220
|
*/
|
|
218
221
|
export interface PayloadStorageConfig {
|
|
@@ -219,6 +219,7 @@ export class Payload extends Construct {
|
|
|
219
219
|
createAssetsBucket() {
|
|
220
220
|
const assetsConfig = this.props.storage?.assets ?? {};
|
|
221
221
|
const assetsProps = {
|
|
222
|
+
// Build output, pruned on every deploy — versioning would only bill for churn.
|
|
222
223
|
versioned: assetsConfig.versioned ?? false,
|
|
223
224
|
deployment: {
|
|
224
225
|
source: `${PAYLOAD_DEFAULTS.SOURCE}/${PAYLOAD_DEFAULTS.OPENNEXT.ASSETS}`,
|
|
@@ -234,6 +235,7 @@ export class Payload extends Construct {
|
|
|
234
235
|
createCacheBucket() {
|
|
235
236
|
const cacheConfig = this.props.storage?.cache ?? {};
|
|
236
237
|
const cacheProps = {
|
|
238
|
+
// ISR cache, regenerated at runtime — versioning would only bill for churn.
|
|
237
239
|
versioned: cacheConfig.versioned ?? false,
|
|
238
240
|
deployment: {
|
|
239
241
|
source: `${PAYLOAD_DEFAULTS.SOURCE}/${PAYLOAD_DEFAULTS.OPENNEXT.CACHE}`,
|
|
@@ -244,8 +246,11 @@ export class Payload extends Construct {
|
|
|
244
246
|
}
|
|
245
247
|
createMediaBucket() {
|
|
246
248
|
const mediaConfig = this.props.storage?.media ?? {};
|
|
249
|
+
// Customer uploads: versioning follows the app backup tier unless set here.
|
|
247
250
|
const mediaProps = {
|
|
248
|
-
|
|
251
|
+
...(mediaConfig.versioned !== undefined && {
|
|
252
|
+
versioned: mediaConfig.versioned
|
|
253
|
+
})
|
|
249
254
|
};
|
|
250
255
|
return this.app.addStorage(StorageFactory.build(`${this.props.name}-media`, mediaProps));
|
|
251
256
|
}
|
|
@@ -33,6 +33,11 @@ export interface S3Props {
|
|
|
33
33
|
readonly bucketName?: string;
|
|
34
34
|
readonly publicReadAccess?: boolean;
|
|
35
35
|
readonly websiteHosting?: WebsiteHostingConfig;
|
|
36
|
+
/**
|
|
37
|
+
* Bucket versioning. Defaults from the effective backup tier — the
|
|
38
|
+
* per-bucket `backupVaultTier`, else the app's `backup: { tier }` — on at
|
|
39
|
+
* `resilient`/`enterprise`, off otherwise. An explicit value always wins.
|
|
40
|
+
*/
|
|
36
41
|
readonly versioned?: boolean;
|
|
37
42
|
readonly encryption?: "AES256" | "KMS";
|
|
38
43
|
readonly kmsKeyArn?: string;
|
|
@@ -42,6 +47,10 @@ export interface S3Props {
|
|
|
42
47
|
* false to opt out. Ignored for non-KMS encryption.
|
|
43
48
|
*/
|
|
44
49
|
readonly bucketKeyEnabled?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Per-bucket backup tier override. Beats the app-level tier for both the
|
|
52
|
+
* `fjall:disasterRecovery:tier` tag and the versioning default.
|
|
53
|
+
*/
|
|
45
54
|
readonly backupVaultTier?: BackupTier;
|
|
46
55
|
readonly cors?: CorsRule[];
|
|
47
56
|
readonly deployment?: S3DeploymentConfig;
|
|
@@ -64,6 +73,12 @@ export interface S3Props {
|
|
|
64
73
|
* construction.
|
|
65
74
|
*/
|
|
66
75
|
readonly appName?: string;
|
|
76
|
+
/**
|
|
77
|
+
* The owning app's backup tier (`App.getApp({ backup: { tier } })`), which
|
|
78
|
+
* the versioning default follows. `StorageFactory` fills it from the App as
|
|
79
|
+
* it does `appName`; pass it only for direct `new Storage(...)` construction.
|
|
80
|
+
*/
|
|
81
|
+
readonly appBackupTier?: BackupTier;
|
|
67
82
|
}
|
|
68
83
|
export interface StorageBuildProps extends S3Props {
|
|
69
84
|
readonly stackPlacement?: "storage" | "cdn" | "compute";
|
|
@@ -84,6 +84,7 @@ export class Storage extends Construct {
|
|
|
84
84
|
bucketKeyEnabled: props.bucketKeyEnabled
|
|
85
85
|
}),
|
|
86
86
|
backupVaultTier: props.backupVaultTier,
|
|
87
|
+
appBackupTier: props.appBackupTier,
|
|
87
88
|
publicReadAccess: props.publicReadAccess,
|
|
88
89
|
websiteHosting: props.websiteHosting,
|
|
89
90
|
resourcePolicyStatements: props.resourcePolicyStatements,
|
|
@@ -218,12 +219,14 @@ export class StorageFactory {
|
|
|
218
219
|
const { stackPlacement, ...s3Props } = props;
|
|
219
220
|
const fn = (app, scope) => {
|
|
220
221
|
validateStorageProps(s3Props);
|
|
221
|
-
// The App is the only place the app name
|
|
222
|
-
// buckets need
|
|
223
|
-
// CdnFactory performs for Cdn
|
|
222
|
+
// The App is the only place the app name and backup tier are known
|
|
223
|
+
// here: website-hosting buckets need the name for their (app,
|
|
224
|
+
// bucket)-keyed exports (the same fill CdnFactory performs for Cdn), and
|
|
225
|
+
// the bucket's versioning default follows the tier.
|
|
224
226
|
return new Storage(scope, id, {
|
|
225
227
|
...s3Props,
|
|
226
|
-
appName: s3Props.appName ?? app.getName()
|
|
228
|
+
appName: s3Props.appName ?? app.getName(),
|
|
229
|
+
appBackupTier: s3Props.appBackupTier ?? app.getBackupTier()
|
|
227
230
|
});
|
|
228
231
|
};
|
|
229
232
|
if (stackPlacement) {
|
|
@@ -46,7 +46,19 @@ export interface ResourcePolicyStatement {
|
|
|
46
46
|
* `bucketKeyEnabled: true` (pass `false` to opt out).
|
|
47
47
|
*/
|
|
48
48
|
export interface S3BucketProps extends BucketProps {
|
|
49
|
+
/**
|
|
50
|
+
* Per-bucket backup tier override. Beats `appBackupTier` for both the
|
|
51
|
+
* `fjall:disasterRecovery:tier` tag and the versioning default.
|
|
52
|
+
*/
|
|
49
53
|
backupVaultTier?: BackupTier;
|
|
54
|
+
/**
|
|
55
|
+
* The owning app's backup tier (`App.getApp({ backup: { tier } })`). The
|
|
56
|
+
* versioning default follows the effective tier — this, unless
|
|
57
|
+
* `backupVaultTier` overrides it — on at `resilient`/`enterprise`. The
|
|
58
|
+
* `StorageFactory` and `BuildkiteFactory` paths fill it from the App; only
|
|
59
|
+
* direct `new S3Bucket` construction needs to pass it.
|
|
60
|
+
*/
|
|
61
|
+
appBackupTier?: BackupTier;
|
|
50
62
|
publicReadAccess?: boolean;
|
|
51
63
|
websiteHosting?: WebsiteHostingConfig;
|
|
52
64
|
/**
|
|
@@ -80,5 +92,7 @@ export interface S3BucketProps extends BucketProps {
|
|
|
80
92
|
}
|
|
81
93
|
export declare class S3Bucket extends Bucket {
|
|
82
94
|
readonly backupVaultTier?: BackupTier;
|
|
95
|
+
/** The tier the bucket runs under: `backupVaultTier`, else `appBackupTier`. */
|
|
96
|
+
readonly effectiveBackupTier?: BackupTier;
|
|
83
97
|
constructor(scope: Construct, id: string, props?: S3BucketProps);
|
|
84
98
|
}
|
|
@@ -3,6 +3,7 @@ import { Annotations, CfnOutput, Duration, RemovalPolicy, Tags } from "aws-cdk-l
|
|
|
3
3
|
import { BlockPublicAccess, Bucket, BucketEncryption } from "aws-cdk-lib/aws-s3";
|
|
4
4
|
import { ArnPrincipal, Effect, PolicyStatement, StarPrincipal } from "aws-cdk-lib/aws-iam";
|
|
5
5
|
import { RegionInfo } from "aws-cdk-lib/region-info";
|
|
6
|
+
import { resolveEffectiveBackupTier, shouldAutoVersion } from "../../../utils/backupTierMapping.js";
|
|
6
7
|
import { toPascalCase } from "../../../utils/capitaliseString.js";
|
|
7
8
|
import { bucketWebsiteEndpointExportName, bucketWebsiteHostedZoneIdExportName } from "@fjall/util";
|
|
8
9
|
import { envAwareRemovalPolicyDefault, toRemovalPolicy } from "../../../utils/removalPolicy.js";
|
|
@@ -15,9 +16,6 @@ export { SDK_PRE_EMPTY_TAG_KEY } from "@fjall/util/aws";
|
|
|
15
16
|
* every site so the retention window cannot drift.
|
|
16
17
|
*/
|
|
17
18
|
export const NONCURRENT_VERSION_EXPIRY_DAYS = 30;
|
|
18
|
-
function shouldAutoVersion(tier) {
|
|
19
|
-
return tier === "resilient" || tier === "enterprise";
|
|
20
|
-
}
|
|
21
19
|
function toResourcePolicyPrincipal(identifier) {
|
|
22
20
|
return identifier === "*"
|
|
23
21
|
? new StarPrincipal()
|
|
@@ -25,10 +23,13 @@ function toResourcePolicyPrincipal(identifier) {
|
|
|
25
23
|
}
|
|
26
24
|
export class S3Bucket extends Bucket {
|
|
27
25
|
backupVaultTier;
|
|
26
|
+
/** The tier the bucket runs under: `backupVaultTier`, else `appBackupTier`. */
|
|
27
|
+
effectiveBackupTier;
|
|
28
28
|
constructor(scope, id, props = {}) {
|
|
29
|
-
const { websiteHosting, backupVaultTier, resourcePolicyStatements, aliasTargetName, appName, ...cdkProps } = props;
|
|
29
|
+
const { websiteHosting, backupVaultTier, appBackupTier, resourcePolicyStatements, aliasTargetName, appName, ...cdkProps } = props;
|
|
30
30
|
const isPublic = props.publicReadAccess === true || websiteHosting !== undefined;
|
|
31
|
-
const
|
|
31
|
+
const effectiveBackupTier = resolveEffectiveBackupTier(backupVaultTier, appBackupTier);
|
|
32
|
+
const versioned = props.versioned ?? shouldAutoVersion(effectiveBackupTier);
|
|
32
33
|
const removalPolicy = props.removalPolicy ?? toRemovalPolicy(envAwareRemovalPolicyDefault());
|
|
33
34
|
// Mirrors the CDK Bucket's own resolution: an encryptionKey with no
|
|
34
35
|
// explicit encryption infers KMS. DSSE is excluded deliberately — S3
|
|
@@ -86,6 +87,7 @@ export class S3Bucket extends Bucket {
|
|
|
86
87
|
]
|
|
87
88
|
});
|
|
88
89
|
this.backupVaultTier = backupVaultTier;
|
|
90
|
+
this.effectiveBackupTier = effectiveBackupTier;
|
|
89
91
|
for (const statement of resourcePolicyStatements ?? []) {
|
|
90
92
|
this.addToResourcePolicy(new PolicyStatement({
|
|
91
93
|
...(statement.sid !== undefined && { sid: statement.sid }),
|
|
@@ -5,7 +5,16 @@ export type BackupTier = "standard" | "resilient" | "enterprise";
|
|
|
5
5
|
/**
|
|
6
6
|
* Shared mapping from application backup tier names to AWS Backup plan tag values.
|
|
7
7
|
*
|
|
8
|
-
* Used by both App.
|
|
9
|
-
* (per-resource
|
|
8
|
+
* Used by both App.applyBackupTier() (app-level tag) and StandardTagsAspect
|
|
9
|
+
* (per-resource tag) to ensure consistent tier → tag translation.
|
|
10
10
|
*/
|
|
11
11
|
export declare const BACKUP_TIER_TAG_MAP: Readonly<Record<BackupTier, string>>;
|
|
12
|
+
/**
|
|
13
|
+
* The tier a resource actually runs under: its own override, else the
|
|
14
|
+
* app-level tier `App.getApp({ backup: { tier } })` declared. The single
|
|
15
|
+
* resolution every tier-derived default reads (S3 versioning, the DR tag),
|
|
16
|
+
* so the derivations cannot drift.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveEffectiveBackupTier(resourceTier: BackupTier | undefined, appTier: BackupTier | undefined): BackupTier | undefined;
|
|
19
|
+
/** Tiers whose S3 buckets version by default. */
|
|
20
|
+
export declare function shouldAutoVersion(tier: BackupTier | undefined): boolean;
|
|
@@ -3,11 +3,24 @@ export const BACKUP_TIER_TAG_KEY = "fjall:disasterRecovery:tier";
|
|
|
3
3
|
/**
|
|
4
4
|
* Shared mapping from application backup tier names to AWS Backup plan tag values.
|
|
5
5
|
*
|
|
6
|
-
* Used by both App.
|
|
7
|
-
* (per-resource
|
|
6
|
+
* Used by both App.applyBackupTier() (app-level tag) and StandardTagsAspect
|
|
7
|
+
* (per-resource tag) to ensure consistent tier → tag translation.
|
|
8
8
|
*/
|
|
9
9
|
export const BACKUP_TIER_TAG_MAP = Object.freeze({
|
|
10
10
|
standard: "default",
|
|
11
11
|
resilient: "resilient",
|
|
12
12
|
enterprise: "enterprise"
|
|
13
13
|
});
|
|
14
|
+
/**
|
|
15
|
+
* The tier a resource actually runs under: its own override, else the
|
|
16
|
+
* app-level tier `App.getApp({ backup: { tier } })` declared. The single
|
|
17
|
+
* resolution every tier-derived default reads (S3 versioning, the DR tag),
|
|
18
|
+
* so the derivations cannot drift.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveEffectiveBackupTier(resourceTier, appTier) {
|
|
21
|
+
return resourceTier ?? appTier;
|
|
22
|
+
}
|
|
23
|
+
/** Tiers whose S3 buckets version by default. */
|
|
24
|
+
export function shouldAutoVersion(tier) {
|
|
25
|
+
return tier === "resilient" || tier === "enterprise";
|
|
26
|
+
}
|
|
@@ -22,10 +22,13 @@ export declare class StandardTagsAspect implements IAspect {
|
|
|
22
22
|
*/
|
|
23
23
|
private addIpamPoolTag;
|
|
24
24
|
/**
|
|
25
|
-
* Add disaster recovery tier tag to S3 buckets
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* Add the disaster recovery tier tag to S3 buckets from the bucket's
|
|
26
|
+
* effective tier — the per-bucket `backupVaultTier` override, else the
|
|
27
|
+
* app-level tier the factory threaded in. CDK child tags override parent
|
|
28
|
+
* tags, so an override wins over the app-level tag set via
|
|
29
|
+
* `App.getApp({ backup })`; an inherited app tier writes the value that tag
|
|
30
|
+
* already carries. The same resolution decides the bucket's versioning
|
|
31
|
+
* default (`S3Bucket`).
|
|
29
32
|
*
|
|
30
33
|
* Tag format: fjall:disasterRecovery:tier = "{tier}"
|
|
31
34
|
*
|
|
@@ -54,10 +54,13 @@ export class StandardTagsAspect {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
|
-
* Add disaster recovery tier tag to S3 buckets
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
57
|
+
* Add the disaster recovery tier tag to S3 buckets from the bucket's
|
|
58
|
+
* effective tier — the per-bucket `backupVaultTier` override, else the
|
|
59
|
+
* app-level tier the factory threaded in. CDK child tags override parent
|
|
60
|
+
* tags, so an override wins over the app-level tag set via
|
|
61
|
+
* `App.getApp({ backup })`; an inherited app tier writes the value that tag
|
|
62
|
+
* already carries. The same resolution decides the bucket's versioning
|
|
63
|
+
* default (`S3Bucket`).
|
|
61
64
|
*
|
|
62
65
|
* Tag format: fjall:disasterRecovery:tier = "{tier}"
|
|
63
66
|
*
|
|
@@ -70,8 +73,8 @@ export class StandardTagsAspect {
|
|
|
70
73
|
let construct = resource;
|
|
71
74
|
while (construct) {
|
|
72
75
|
const backupConstruct = construct;
|
|
73
|
-
if (backupConstruct.
|
|
74
|
-
const tier = backupConstruct.
|
|
76
|
+
if (backupConstruct.effectiveBackupTier) {
|
|
77
|
+
const tier = backupConstruct.effectiveBackupTier;
|
|
75
78
|
const tagValue = BACKUP_TIER_TAG_MAP[tier];
|
|
76
79
|
Tags.of(resource).add(BACKUP_TIER_TAG_KEY, tagValue);
|
|
77
80
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/components-infrastructure",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "30.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/fjall-tech/fjall.git",
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
80
|
"@aws-sdk/client-organizations": "^3.1098.0",
|
|
81
|
-
"@fjall/generator": "^
|
|
82
|
-
"@fjall/util": "^
|
|
81
|
+
"@fjall/generator": "^30.0.0",
|
|
82
|
+
"@fjall/util": "^30.0.0",
|
|
83
83
|
"constructs": "^10.7.2",
|
|
84
84
|
"zod": "^4.4.3"
|
|
85
85
|
},
|