> ## Documentation Index
> Fetch the complete documentation index at: https://cdktn.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Aspects - CDK Terrain

> Aspects apply an operation to all constructs in a given scope. You can use them for mutation (e.g., add resource tags) and validation (e.g., check encryption).

Aspects allow you to apply an operation to all constructs within a given scope. You may want to use them in your CDK Terrain (CDKTN) 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 CDKTN application descends from the `Construct` class, so you could call the construct on any instantiated element. This includes the entire application, a particular [stack](/concepts/stacks), or all of the resources for a specific [provider](/concepts/providers). When you call the aspect, CDKTN 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.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { Construct, IConstruct } from "constructs";
  import { Aspects, IAspect, TerraformStack } from "cdktn";
  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: "cdktn",
        },
      });

      // Add tags to every resource defined within this stack.
      Aspects.of(this).add(new TagsAddingAspect({ createdBy: "cdktn" }));
    }
  }
  ```

  ```java Java theme={null}
      public class TagsAddingAspect implements IAspect {
          private final Map<String, String> tags;

          public TagsAddingAspect(Map<String, String> tags) {
              this.tags = tags;
          }

          public Method getInputTags(IConstruct node) {
              Method getTagsInput;
              try {
                  getTagsInput = node.getClass().getDeclaredMethod("getTagsInput");
              } catch (NoSuchMethodException e) {
                  // Not all constructs are taggable, so we need to filter it
                  throw new RuntimeException("Resource " + node.getClass() + " is un-taggable");
              }
              getTagsInput.setAccessible(true);
              return getTagsInput;
          }

          public Method getTagsSet(IConstruct node) {
              Method setTags;
              try {
                  setTags = node.getClass().getDeclaredMethod("setTags", Map.class);
              } catch (NoSuchMethodException e) {
                  // Not all constructs are taggable, so we need to filter it
                  throw new RuntimeException("Resource " + node.getClass() + " is un-taggable");
              }
              setTags.setAccessible(true);
              return setTags;
          }

          // This method is called on every Construct within the defined scope (resource,
          // data sources, etc.).
          public void visit(IConstruct node) {
              Method getTagsInput = getInputTags(node);
              Method setTags = getTagsSet(node);
              Map<String, String> currentTags;
              try {
                  // We need to take the input value to not create a circular reference
                  currentTags = (Map<String, String>) getTagsInput.invoke(node);
              } catch (IllegalAccessException e) {
                  throw new RuntimeException(e);
              } catch (InvocationTargetException e) {
                  throw new RuntimeException(e);
              }
              try {
                  HashMap<String, String> newTags = new HashMap<>();
                  newTags.putAll(this.tags);
                  if (currentTags != null) {
                      newTags.putAll(currentTags);
                  }
                  setTags.invoke(node, newTags);
              } catch (IllegalAccessException e) {
                  throw new RuntimeException(e);
              } catch (InvocationTargetException e) {
                  throw new RuntimeException(e);
              }
          }
      }

          // Add tags to every resource defined within `myStack`.
          Aspects.of(bucket).add(new TagsAddingAspect(new HashMap<String, String>() {
              {
                  put("createdBy", "cdktn");
              }
          }));
  ```

  ```python Python theme={null}
  import jsii
  from constructs import Construct, IConstruct
  from cdktn import TerraformStack, Aspects, IAspect
  from imports.aws.instance import Instance
  from imports.aws.provider import AwsProvider

  @jsii.implements(IAspect)
  class TagsAddingAspect:
      def __init__(self, tagsToAdd):
          self.tagsToAdd = tagsToAdd

      # This method is called on every Construct within the specified scope (resources, data sources, etc.).
      def visit(self, node: IConstruct):
          # Not all constructs are taggable, so we need to check
          if(hasattr(node, "tags") and hasattr(node, "tags_input")):
              # We need to take the input value to not create a circular reference
              currentTags = node.tags_input if node.tags_input is not None else {}
              node.tags = {**self.tagsToAdd, **currentTags}

  class MySingleStack(TerraformStack):
      def __init__(self, scope: Construct, id: str):
          super().__init__(scope, id)
          AwsProvider(self, "aws",
              region = "us-east-1"
          )
          Instance(self, "Hello",
              ami = "ami-2757f631",
              instance_type = "t2.micro"
          )

          # Add tags to every resource defined within `MySingleStack`.
          Aspects.of(self).add(TagsAddingAspect({ "createdBy": "cdktn" }))

          # Add tags to every resource defined within `myStack`.
          Aspects.of(myStack).add(TagsAddingAspect({ "createdBy": "cdktn" }))
  ```

  ```csharp C# theme={null}
  using System.Collections.Generic;
  using Constructs;
  using Io.Cdktn;
  using aws.S3Bucket;
  using aws.Provider;
  using Amazon.JSII.Runtime;

  namespace Examples
  {
      public class TagsAddingAspect : Amazon.JSII.Runtime.Deputy.DeputyBase, IAspect
      {
          private readonly IDictionary<string, string> tagsToAdd;

          public TagsAddingAspect(IDictionary<string, string> tagsToAdd)
          {
              this.tagsToAdd = tagsToAdd;
          }

          public void Visit(IConstruct node)
          {
              var nodeType = node.GetType();
              var tagsProperty = nodeType.GetProperty("Tags");
              var tagsInputProperty = nodeType.GetProperty("TagsInput");
              if (tagsProperty != null && tagsInputProperty != null)
              {
                  var inputTags = (IDictionary<string, string>)tagsInputProperty.GetValue(node);

                  IDictionary<string, string> newTags;
                  if (inputTags == null)
                  {
                      newTags = tagsToAdd;
                  }
                  else
                  {
                      newTags = new Dictionary<string, string>(tagsToAdd);
                      foreach ((string key, string value) in inputTags)
                      {
                          newTags[key] = value;
                      }
                  }

                  tagsProperty.SetValue(node, newTags);
              }
          }
      }

      class MyAspectsStack : TerraformStack
      {
          public MyAspectsStack(Construct scope, string name) : base(scope, name)
          {
              new AwsProvider(this, "Aws", new AwsProviderConfig
              {
                  Region = "us-east-1"
              });
              S3Bucket bucket = new S3Bucket(this, "bucket", new S3BucketConfig
              {
                  Bucket = "demo"
              });

              Aspects.Of(this).Add(new TagsAddingAspect(new Dictionary<string, string> {
                  { "createdBy", "cdktn" }
              }));
          }
      }
  }
  ```

  ```go Go theme={null}
  package main

  import (
  	"github.com/aws/constructs-go/constructs/v10"
  	"github.com/aws/jsii-runtime-go"
  	"github.com/open-constructs/cdk-terrain-go/cdktn"
  	aws "github.com/open-constructs/cdk-terrain/examples/go/documentation/generated/hashicorp/aws/provider"
  	"github.com/open-constructs/cdk-terrain/examples/go/documentation/generated/hashicorp/aws/s3bucket"
  	"golang.org/x/exp/maps"
  )

  type Taggable interface {
  	TagsInput() *map[string]*string
  	SetTags(val *map[string]*string)
  }

  type TagsAddingAspect struct {
  	Tags *map[string]*string
  }

  func (taa TagsAddingAspect) Visit(node constructs.IConstruct) {
  	if taggable, ok := node.(Taggable); ok {
  		existing := *taggable.TagsInput()
  		tags := *taa.Tags
  		maps.Copy(existing, tags) // requires Go 1.18
  		taggable.SetTags(&existing)
  	}
  }

  func NewTagsAddingAspect(tags *map[string]*string) *TagsAddingAspect {
  	return &TagsAddingAspect{Tags: tags}
  }

  func NewAspectsStack(scope constructs.Construct, name string) cdktn.TerraformStack {
  	stack := cdktn.NewTerraformStack(scope, &name)

  	aws.NewAwsProvider(stack, jsii.String("aws"), &aws.AwsProviderConfig{
  		Region: jsii.String("us-east-1"),
  	})

  	s3bucket.NewS3Bucket(stack, jsii.String("bucket"), &s3bucket.S3BucketConfig{
  		Bucket: jsii.String("demo"),
  		Tags: &map[string]*string{
  			"owner": jsii.String("cdktn"),
  		},
  	})

  	cdktn.Aspects_Of(stack).Add(
  		NewTagsAddingAspect(&map[string]*string{"createdBy": jsii.String("cdktn")}),
  	)

  	return stack
  }

  ```
</CodeGroup>

You can also use aspects for validation. The following example defines an aspect that checks whether all S3 Buckets start with the correct prefix.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { Annotations, Aspects, IAspect, TerraformStack } from "cdktn";
  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`.
          // CDKTN 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"));
    }
  }
  ```

  ```java Java theme={null}
      public class ValidateS3IsPrefixed implements IAspect {

          private final String prefix;

          public ValidateS3IsPrefixed(String prefix) {
              this.prefix = prefix;
          }

          // This method is called on every Construct within the defined scope (resource,
          // data sources, etc.).
          public void visit(IConstruct node) {
              if (node instanceof S3Bucket) {
                  if (((S3Bucket) node).getBucketInput() != null && !((S3Bucket) node).getBucketInput().startsWith(this.prefix)) {
                      // You can include `addInfo`, `addWarning`, and `addError`.
                      // CDKTN 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);
                  }
              }
          }
      }

          // Check the prefix for every resource within `myStack`.
          Aspects.of(bucket).add(new ValidateS3IsPrefixed("myPrefix"));
  ```

  ```python Python theme={null}
  from constructs import IConstruct
  from cdktn import Aspects, IAspect, Annotations
  from imports.aws.s3_bucket import S3Bucket

  @jsii.implements(IAspect)
  class ValidateS3IsPrefixed:
      def __init__(self, prefix: str):
          self.prefix = prefix

      # This method is called on every Construct within the defined scope (resource, data sources, etc.).
      def visit(self, node: IConstruct):
          if isinstance(node, S3Bucket):
              if(node.bucket_input and not node.bucket_input.startswith(self.prefix)):
                # You can include `addInfo`, `addWarning`, and `addError`.
                # CDKTN prints these messages when the user runs `synth`, `plan`, or `deploy`.
                Annotations.of(node).add_error(
                  "Each S3 Bucket name needs to start with {}".format(self.prefix)
                )

          # Check the prefix for every resource within `myStack`.
          Aspects.of(myStack).add(ValidateS3IsPrefixed("myPrefix"))
  ```

  ```csharp C# theme={null}
  using System.Collections.Generic;
  using Constructs;
  using Io.Cdktn;
  using aws.S3Bucket;
  using aws.Provider;
  using Amazon.JSII.Runtime;

  namespace Examples
  {
      public class ValidateS3IsPrefixed : Amazon.JSII.Runtime.Deputy.DeputyBase, IAspect
      {
          private readonly string prefix;

          public ValidateS3IsPrefixed(string prefix)
          {
              this.prefix = prefix;
          }

          public void Visit(IConstruct node)
          {
              if (node is S3Bucket bucket)
              {
                  if (bucket.BucketInput != null && !bucket.BucketInput.StartsWith(this.prefix))
                  {
                      Annotations.Of(node).AddError($"Each S3 Bucket name needs to start with {this.prefix}");
                  }
              }
          }
      }

      class MyAspectsValidationStack : TerraformStack
      {
          public MyAspectsValidationStack(Construct scope, string name) : base(scope, name)
          {
              new AwsProvider(this, "Aws", new AwsProviderConfig
              {
                  Region = "us-east-1"
              });
              S3Bucket bucket = new S3Bucket(this, "bucket", new S3BucketConfig
              {
                  Bucket = "myPrefixDemo"
              });

              Aspects.Of(this).Add(new ValidateS3IsPrefixed("myPrefix"));
          }
      }
  }
  ```

  ```go Go theme={null}
  package main

  import (
  	"strings"

  	"github.com/aws/constructs-go/constructs/v10"
  	"github.com/aws/jsii-runtime-go"
  	"github.com/open-constructs/cdk-terrain-go/cdktn"
  	aws "github.com/open-constructs/cdk-terrain/examples/go/documentation/generated/hashicorp/aws/provider"
  	"github.com/open-constructs/cdk-terrain/examples/go/documentation/generated/hashicorp/aws/s3bucket"
  )

  type S3Bucket interface {
  	GetBucketInput() *string
  }

  type ValidateS3IsPrefixedAspect struct {
  	Prefix string
  }

  func (vpa ValidateS3IsPrefixedAspect) Visit(node constructs.IConstruct) {
  	if bucket, ok := node.(s3bucket.S3Bucket); ok && strings.HasPrefix(*bucket.BucketInput(), vpa.Prefix) {
  		cdktn.Annotations_Of(node).AddError(jsii.String("Each S3 Bucket name needs to start with " + vpa.Prefix))
  	}
  }

  func NewValidateS3IsPrefixedAspect(prefix string) *ValidateS3IsPrefixedAspect {
  	return &ValidateS3IsPrefixedAspect{Prefix: prefix}
  }

  func NewPrefixAspectsStack(scope constructs.Construct, name string) cdktn.TerraformStack {
  	stack := cdktn.NewTerraformStack(scope, &name)

  	aws.NewAwsProvider(stack, jsii.String("aws"), &aws.AwsProviderConfig{
  		Region: jsii.String("us-east-1"),
  	})

  	s3bucket.NewS3Bucket(stack, jsii.String("bucket"), &s3bucket.S3BucketConfig{
  		Bucket: jsii.String("myPrefixDemo"),
  	})

  	cdktn.Aspects_Of(stack).Add(NewValidateS3IsPrefixedAspect("myPrefix"))

  	return stack
  }

  ```
</CodeGroup>
