or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotations-aspects.mdbackend-configuration.mdcore-infrastructure.mdindex.mditerators-dynamic.mdproviders-modules.mdprovisioners.mdresources-data-sources.mdterraform-functions.mdtesting.mdtokens-expressions.mdvariables-outputs.md

annotations-aspects.mddocs/

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

```