Terraform
Aspects
Aspects allow you to apply an operation to all constructs within a given scope. You may want to use them in your CDK for Terraform (CDKTF) application to mutate elements (e.g., add tags to cloud resources) or for validation (e.g., ensure all S3 Buckets are encrypted).
Define Aspects
To create an aspect, you must import the Aspects
class and the IAspect
interface and implement one or more methods for IAspect
. Then, you can call the aspect one or more times on any construct within your application.
Everything within a CDKTF application descends from the Construct
class, so you could call the construct on any instantiated element. This includes the entire application, a particular stack, or all of the resources for a specific provider. When you call the aspect, CDKTF applies its methods to all of the the constructs that fall within the specified scope.
The following example defines an aspect to add tags to resources.
import { Construct, IConstruct } from "constructs";
import { Aspects, IAspect, TerraformStack } from "cdktf";
import { AwsProvider } from "./.gen/providers/aws/provider";
import { S3Bucket } from "./.gen/providers/aws/s3-bucket";
// Not all constructs are taggable, so we need to filter it
type TaggableConstruct = IConstruct & {
tags?: { [key: string]: string };
tagsInput?: { [key: string]: string };
};
function isTaggableConstruct(x: IConstruct): x is TaggableConstruct {
return "tags" in x && "tagsInput" in x;
}
export class TagsAddingAspect implements IAspect {
constructor(private tagsToAdd: Record<string, string>) {}
// This method is called on every Construct within the specified scope (resources, data sources, etc.).
visit(node: IConstruct) {
if (isTaggableConstruct(node)) {
// We need to take the input value to not create a circular reference
const currentTags = node.tagsInput || {};
node.tags = { ...this.tagsToAdd, ...currentTags };
}
}
}
export class AspectTaggingStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name);
new AwsProvider(this, "aws", {
region: "us-west-2",
});
new S3Bucket(this, "bucket", {
bucket: "demo",
tags: {
owner: "cdktf",
},
});
// Add tags to every resource defined within this stack.
Aspects.of(this).add(new TagsAddingAspect({ createdBy: "cdktf" }));
}
}
You can also use aspects for validation. The following example defines an aspect that checks whether all S3 Buckets start with the correct prefix.
import { Annotations, Aspects, IAspect, TerraformStack } from "cdktf";
import { Construct, IConstruct } from "constructs";
import { AwsProvider } from "./.gen/providers/aws/provider";
import { S3Bucket } from "./.gen/providers/aws/s3-bucket";
export class ValidateS3IsPrefixed implements IAspect {
constructor(private prefix: string) {}
// This method is called on every Construct within the defined scope (resource, data sources, etc.).
visit(node: IConstruct) {
if (node instanceof S3Bucket) {
if (node.bucketInput && !node.bucketInput.startsWith(this.prefix)) {
// You can include `addInfo`, `addWarning`, and `addError`.
// CDKTF prints these messages when the user runs `synth`, `plan`, or `deploy`.
Annotations.of(node).addError(
`Each S3 Bucket name needs to start with ${this.prefix}`
);
}
}
}
}
export class AspectValidationStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name);
new AwsProvider(this, "aws", {
region: "us-west-2",
});
new S3Bucket(this, "bucket", {
bucket: "myPrefixDemo",
});
Aspects.of(this).add(new ValidateS3IsPrefixed("myPrefix"));
}
}