0
# Annotations and Aspects
1
2
Construct annotation system for adding metadata and implementing cross-cutting concerns through aspect-oriented programming patterns.
3
4
## Capabilities
5
6
### Annotations Class
7
8
System for adding informational messages, warnings, and errors to constructs for documentation and validation purposes.
9
10
```typescript { .api }
11
/**
12
* Annotation system for constructs
13
*/
14
class Annotations {
15
/**
16
* Get the annotations instance for a construct
17
* @param scope - The construct to get annotations for
18
* @returns Annotations instance
19
*/
20
static of(scope: IConstruct): Annotations;
21
22
/**
23
* Add an informational message
24
* @param message - Information message
25
*/
26
addInfo(message: string): void;
27
28
/**
29
* Add a warning message
30
* @param message - Warning message
31
*/
32
addWarning(message: string): void;
33
34
/**
35
* Add an error message
36
* @param message - Error message
37
*/
38
addError(message: string): void;
39
40
/**
41
* Get all annotations for this construct
42
*/
43
readonly all: AnnotationEntry[];
44
}
45
```
46
47
**Usage Examples:**
48
49
```typescript
50
import { Annotations } from "cdktf";
51
52
class MyStack extends TerraformStack {
53
constructor(scope: Construct, id: string) {
54
super(scope, id);
55
56
const instance = new AwsInstance(this, "web", {
57
ami: "ami-12345678",
58
instanceType: "t2.micro"
59
});
60
61
// Add informational annotation
62
Annotations.of(instance).addInfo(
63
"This instance is configured with default security settings"
64
);
65
66
// Add warning for potential issues
67
if (!instance.keyName) {
68
Annotations.of(instance).addWarning(
69
"No SSH key specified - remote access will not be possible"
70
);
71
}
72
73
// Add error for invalid configurations
74
if (instance.instanceType.startsWith("t1.")) {
75
Annotations.of(instance).addError(
76
"t1 instance types are deprecated and should not be used"
77
);
78
}
79
80
// Stack-level annotations
81
Annotations.of(this).addInfo(
82
"Stack created for development environment"
83
);
84
}
85
}
86
```
87
88
### Aspects System
89
90
Aspect-oriented programming system for implementing cross-cutting concerns that apply to multiple constructs.
91
92
```typescript { .api }
93
/**
94
* Interface for implementing aspects
95
*/
96
interface IAspect {
97
/**
98
* Visit a construct and apply the aspect
99
* @param node - The construct to visit
100
*/
101
visit(node: IConstruct): void;
102
}
103
104
/**
105
* Aspects management for constructs
106
*/
107
class Aspects {
108
/**
109
* Get the aspects instance for a construct
110
* @param scope - The construct to get aspects for
111
* @returns Aspects instance
112
*/
113
static of(scope: IConstruct): Aspects;
114
115
/**
116
* Add an aspect to this construct
117
* @param aspect - The aspect to add
118
*/
119
add(aspect: IAspect): void;
120
121
/**
122
* Get all aspects applied to this construct
123
*/
124
readonly all: IAspect[];
125
}
126
```
127
128
**Usage Examples:**
129
130
```typescript
131
import { Aspects, IAspect, Annotations } from "cdktf";
132
133
// Security aspect for validating security groups
134
class SecurityGroupValidationAspect implements IAspect {
135
visit(node: IConstruct): void {
136
if (node instanceof AwsSecurityGroup) {
137
const sg = node as AwsSecurityGroup;
138
139
// Check for overly permissive rules
140
if (sg.ingress?.some(rule =>
141
rule.cidrBlocks?.includes("0.0.0.0/0") && rule.fromPort === 22
142
)) {
143
Annotations.of(node).addWarning(
144
"Security group allows SSH access from anywhere (0.0.0.0/0)"
145
);
146
}
147
148
// Check for missing egress rules
149
if (!sg.egress || sg.egress.length === 0) {
150
Annotations.of(node).addInfo(
151
"No explicit egress rules defined - will use default allow-all"
152
);
153
}
154
}
155
}
156
}
157
158
// Tagging aspect for consistent resource tagging
159
class TaggingAspect implements IAspect {
160
constructor(
161
private readonly tags: {[key: string]: string}
162
) {}
163
164
visit(node: IConstruct): void {
165
// Apply to resources that support tags
166
if (node instanceof TerraformResource) {
167
const resource = node as any;
168
if (resource.tags !== undefined) {
169
resource.tags = {
170
...this.tags,
171
...resource.tags
172
};
173
174
Annotations.of(node).addInfo(
175
`Applied standard tags: ${Object.keys(this.tags).join(", ")}`
176
);
177
}
178
}
179
}
180
}
181
182
// Cost optimization aspect
183
class CostOptimizationAspect implements IAspect {
184
visit(node: IConstruct): void {
185
if (node instanceof AwsInstance) {
186
const instance = node as AwsInstance;
187
188
// Recommend smaller instance types for development
189
if (instance.instanceType?.startsWith("m5.")) {
190
Annotations.of(node).addWarning(
191
"Consider using t3 instance types for cost optimization"
192
);
193
}
194
195
// Check for missing monitoring
196
if (!instance.monitoring) {
197
Annotations.of(node).addInfo(
198
"Enable detailed monitoring for better cost visibility"
199
);
200
}
201
}
202
203
if (node instanceof AwsEbsVolume) {
204
const volume = node as AwsEbsVolume;
205
206
// Recommend gp3 over gp2
207
if (volume.type === "gp2") {
208
Annotations.of(node).addWarning(
209
"Consider upgrading to gp3 volume type for better price/performance"
210
);
211
}
212
}
213
}
214
}
215
216
// Apply aspects to stack
217
class MyStack extends TerraformStack {
218
constructor(scope: Construct, id: string, props: MyStackProps) {
219
super(scope, id);
220
221
// Apply tagging aspect to all resources in this stack
222
Aspects.of(this).add(new TaggingAspect({
223
Environment: props.environment,
224
Project: props.project,
225
ManagedBy: "terraform",
226
CostCenter: props.costCenter
227
}));
228
229
// Apply security validation
230
Aspects.of(this).add(new SecurityGroupValidationAspect());
231
232
// Apply cost optimization recommendations
233
Aspects.of(this).add(new CostOptimizationAspect());
234
235
// Create resources - aspects will be applied automatically
236
const vpc = new AwsVpc(this, "vpc", {
237
cidrBlock: "10.0.0.0/16"
238
});
239
240
const securityGroup = new AwsSecurityGroup(this, "web-sg", {
241
vpcId: vpc.id,
242
ingress: [{
243
fromPort: 80,
244
toPort: 80,
245
protocol: "tcp",
246
cidrBlocks: ["0.0.0.0/0"]
247
}]
248
});
249
250
const instance = new AwsInstance(this, "web", {
251
ami: "ami-12345678",
252
instanceType: "t3.micro",
253
vpcSecurityGroupIds: [securityGroup.id]
254
});
255
}
256
}
257
```
258
259
### Advanced Aspect Patterns
260
261
#### Conditional Aspects
262
263
```typescript
264
class ConditionalSecurityAspect implements IAspect {
265
constructor(
266
private readonly environment: string
267
) {}
268
269
visit(node: IConstruct): void {
270
// Only apply strict security in production
271
if (this.environment === "production") {
272
if (node instanceof AwsInstance) {
273
const instance = node as AwsInstance;
274
275
if (!instance.keyName) {
276
Annotations.of(node).addError(
277
"Production instances must have SSH key configured"
278
);
279
}
280
281
if (instance.associatePublicIpAddress) {
282
Annotations.of(node).addError(
283
"Production instances should not have public IP addresses"
284
);
285
}
286
}
287
}
288
}
289
}
290
291
// Apply only to production stacks
292
if (environment === "production") {
293
Aspects.of(stack).add(new ConditionalSecurityAspect(environment));
294
}
295
```
296
297
#### Resource Transformation Aspect
298
299
```typescript
300
class EncryptionAspect implements IAspect {
301
visit(node: IConstruct): void {
302
// Automatically enable encryption for EBS volumes
303
if (node instanceof AwsEbsVolume) {
304
const volume = node as AwsEbsVolume;
305
if (!volume.encrypted) {
306
volume.encrypted = true;
307
Annotations.of(node).addInfo("Encryption enabled automatically");
308
}
309
}
310
311
// Enable encryption for S3 buckets
312
if (node instanceof AwsS3Bucket) {
313
const bucket = node as AwsS3Bucket;
314
if (!bucket.serverSideEncryptionConfiguration) {
315
bucket.serverSideEncryptionConfiguration = [{
316
rule: [{
317
applyServerSideEncryptionByDefault: [{
318
sseAlgorithm: "AES256"
319
}]
320
}]
321
}];
322
Annotations.of(node).addInfo("Server-side encryption enabled automatically");
323
}
324
}
325
}
326
}
327
```
328
329
#### Validation Aspect with Custom Logic
330
331
```typescript
332
class NamingConventionAspect implements IAspect {
333
constructor(
334
private readonly prefix: string,
335
private readonly environment: string
336
) {}
337
338
visit(node: IConstruct): void {
339
if (node instanceof TerraformResource) {
340
const resource = node as any;
341
342
// Check naming convention for resources with names
343
if (resource.name && typeof resource.name === "string") {
344
const expectedPrefix = `${this.prefix}-${this.environment}`;
345
346
if (!resource.name.startsWith(expectedPrefix)) {
347
Annotations.of(node).addWarning(
348
`Resource name should start with "${expectedPrefix}" for consistency`
349
);
350
}
351
}
352
353
// Validate tag consistency
354
if (resource.tags) {
355
const requiredTags = ["Environment", "Project"];
356
const missingTags = requiredTags.filter(tag => !resource.tags[tag]);
357
358
if (missingTags.length > 0) {
359
Annotations.of(node).addError(
360
`Missing required tags: ${missingTags.join(", ")}`
361
);
362
}
363
}
364
}
365
}
366
}
367
```
368
369
### Built-in Aspects
370
371
CDKTF includes several built-in aspects for common use cases:
372
373
```typescript { .api }
374
/**
375
* Built-in aspect for validating resource configurations
376
*/
377
class ValidationAspect implements IAspect {
378
visit(node: IConstruct): void;
379
}
380
381
/**
382
* Built-in aspect for dependency validation
383
*/
384
class DependencyValidationAspect implements IAspect {
385
visit(node: IConstruct): void;
386
}
387
```
388
389
## Type Definitions
390
391
```typescript { .api }
392
/**
393
* Annotation entry with metadata
394
*/
395
interface AnnotationEntry {
396
/**
397
* Type of annotation
398
*/
399
readonly type: AnnotationMetadataEntryType;
400
401
/**
402
* Annotation message
403
*/
404
readonly message: string;
405
406
/**
407
* Stack trace where annotation was created
408
*/
409
readonly trace: string[];
410
411
/**
412
* Additional metadata
413
*/
414
readonly data?: any;
415
}
416
417
/**
418
* Annotation types
419
*/
420
enum AnnotationMetadataEntryType {
421
INFO = "info",
422
WARN = "warn",
423
ERROR = "error"
424
}
425
426
/**
427
* Interface for constructs that can have annotations and aspects
428
*/
429
interface IConstruct {
430
/**
431
* Construct identifier
432
*/
433
readonly node: ConstructNode;
434
435
/**
436
* Construct path in the tree
437
*/
438
readonly constructPath: string;
439
}
440
441
/**
442
* Construct node providing tree navigation
443
*/
444
interface ConstructNode {
445
/**
446
* All child constructs
447
*/
448
readonly children: IConstruct[];
449
450
/**
451
* Parent construct
452
*/
453
readonly scope?: IConstruct;
454
455
/**
456
* Construct ID
457
*/
458
readonly id: string;
459
460
/**
461
* Full path from root
462
*/
463
readonly path: string;
464
}
465
```