» Resource Tagging

Many AWS services implement resource tags as an essential part of managing components. These arbitrary key-value pairs can be utilized for billing, ownership, automation, access control, and many other use cases. Given that these tags are an important aspect of successfully managing an AWS environment, the Terraform AWS Provider implements additional functionality beyond the typical one-to-one resource lifecycle management for easier and more customized implementations.

» Getting Started with Resource Tags

Terraform AWS Provider resources that support resource tags implement a consistent argument named tags which accepts a key-value map, e.g.

resource "aws_vpc" "example" {
  # ... other configuration ...

  tags = {
    Name = "MyVPC"
  }
}

The tags for the resource are wholly managed by Terraform except tag keys beginning with aws: as these are managed by AWS services and cannot typically be edited or deleted. Any non-AWS tags added to the VPC outside of Terraform will be proposed for removal on the next Terraform execution. Missing tags or those with incorrect values from the Terraform configuration will be proposed for addition or update on the next Terraform execution. Advanced patterns that can adjust these behaviors for special use cases, such as Terraform AWS Provider configurations that affect all resources and the ability to manage resource tags for resources not managed by Terraform, can be found later in this guide.

For most environments and use cases, this is the typical implementation pattern, whether it be in a standalone Terraform configuration or within a Terraform Module. The Terraform configuration language also enables less repetitive configurations via variables, locals, or potentially a combination of these, e.g.

# Terraform 0.12 and later syntax
variable "additional_tags" {
  default     = {}
  description = "Additional resource tags"
  type        = map(string)
}

resource "aws_vpc" "example" {
  # ... other configuration ...

  # This configuration combines some "default" tags with optionally provided additional tags
  tags = merge(
    var.additional_tags,
    {
      Name = "MyVPC"
    },
  )
}

» Ignoring Changes to Specific Tags

Systems outside of Terraform may automatically interact with the tagging associated with AWS resources. These external systems may be for administrative purposes, such as a Configuration Management Database, or the tagging may be required functionality for those systems, such as Kubernetes. This section shows methods to prevent Terraform from showing differences for specific tags.

» Ignoring Changes in Individual Resources

All Terraform resources support the lifecycle configuration block ignore_changes argument, which can be used to explicitly ignore all tags changes on a resource beyond an initial configuration or individual tag values.

In this example, the Name tag will be added to the VPC on resource creation, however any external changes to the Name tag value or the addition/removal of any tag (including the Name tag) will be ignored:

# Terraform 0.12 and later syntax
resource "aws_vpc" "example" {
  # ... other configuration ...

  tags = {
    Name = "MyVPC"
  }

  lifecycle {
    ignore_tags = [tags]
  }
}

In this example, the Name and Owner tags will be added to the VPC on resource creation, however any external changes to the value of the Name tag will be ignored while any changes to other tags (including the Owner tag and any additions) will still be proposed:

# Terraform 0.12 and later syntax
resource "aws_vpc" "example" {
  # ... other configuration ...

  tags = {
    Name  = "MyVPC"
    Owner = "Operations"
  }

  lifecycle {
    ignore_tags = [tags["Name"]]
  }
}

» Ignoring Changes in All Resources

As of version 2.60.0 of the Terraform AWS Provider, there is support for ignoring tag changes across all resources under a provider. This simplifies situations where certain tags may be externally applied more globally and enhances functionality beyond ignore_changes to support cases such as tag key prefixes.

In this example, all resources will ignore any addition of the LastScanned tag:

provider "aws" {
  # ... potentially other configuration ...

  ignore_tags {
    keys = ["LastScanned"]
  }
}

In this example, all resources will ignore any addition of tags with the kubernetes.io/ prefix, such as kubernetes.io/cluster/name or kubernetes.io/role/elb:

provider "aws" {
  # ... potentially other configuration ...

  ignore_tags {
    key_prefixes = ["kubernetes.io/"]
  }
}

Any of the ignore_tags configurations can be combined as needed.

The provider ignore tags configuration applies to all Terraform AWS Provider resources under that particular instance (the default provider instance in the above cases). If multiple, different Terraform AWS Provider configurations are being used (e.g. multiple provider instances), the ignore tags configuration must be added to all applicable provider configurations.

» Managing Individual Resource Tags

Certain Terraform AWS Provider services support a special resource for managing an individual tag on a resource without managing the resource itself. One example is the aws_ec2_tag resource. These resources enable tagging where resources are created outside Terraform such as EC2 Images (AMIs), shared across accounts via Resource Access Manager (RAM), or implicitly created by other means such as EC2 VPN Connections implicitly creating a taggable EC2 Transit Gateway VPN Attachment.

# Terraform 0.12 and later syntax
# ... other configuration ...

resource "aws_ec2_tag" "example" {
  resource_id = aws_vpn_connection.example.transit_gateway_attachment_id
  key         = "Owner"
  value       = "Operations"
}

To manage multiple tags for a resource in this scenario, for_each can be used:

# Terraform 0.12 and later syntax
# ... other configuration ...

resource "aws_ec2_tag" "example" {
  for_each = {"Name": "MyAttachment", "Owner": "Operations"}

  resource_id = aws_vpn_connection.example.transit_gateway_attachment_id
  key         = each.key
  value       = each.value
}

The inline map provided to for_each in the example above is used for brevity, but other Terraform configuration language features similar to those noted at the beginning of this guide can be used to make the example more extensible.