Tagging Implementation
Relevant source files
The following files were used as context for generating this wiki page:
- infrastructure/repository/labels-workflow.tf
- internal/generate/tagresource/README.md
- internal/generate/tagresource/main.go
- internal/generate/tags/README.md
- internal/generate/tags/README_keyvaluetags.md
- internal/generate/tags/README_listtags.md
- internal/generate/tags/README_servicetags.md
- internal/generate/tags/README_updatetags.md
- internal/generate/tags/main.go
- internal/service/appconfig/deployment_test.go
- internal/service/codebuild/project_test.go
- internal/service/codecommit/approval_rule_template_test.go
- internal/service/ec2/generate/createtags/main.go
- internal/service/resourcegroupstaggingapi/README.md
- internal/service/sagemaker/flow_definition_test.go
- internal/service/schemas/schema_test.go
- internal/service/swf/domain_test.go
- internal/service/waf/generate.go
- internal/service/wafv2/generate.go
- internal/service/worklink/tags_gen.go
The tagging implementation within the Terraform AWS Provider is a standardized system designed to provide a consistent interface for handling AWS resource tags. It addresses inconsistencies across various AWS service APIs, where some services use a map[string]string for tags while others use a slice of custom structs (e.g., []*ec2.Tag). The core of this system is an abstraction layer coupled with a powerful code generation framework that automatically creates service-specific helper functions for tag management.
This approach significantly reduces boilerplate code and ensures that tagging logic for creating, listing, and updating tags is handled uniformly across the provider. The system is built around the tftags.KeyValueTags type, which provides a unified way to manage tag data, and a set of generators that produce tailored code for each AWS service based on directives in generate.go files.
Sources: internal/generate/tags/README_keyvaluetags.md, internal/generate/tags/README.md
Code Generation Framework
The tagging system relies heavily on Go's //go:generate directives to automate the creation of tagging helper functions. This framework is essential for maintaining consistency and reducing manual effort when adding or updating tagging support for AWS services.
Sources: internal/generate/tags/README.md
Generation Flow
The code generation process is initiated by the make gen command, which executes go generate across the provider's codebase. The main tagging generator, internal/generate/tags/main.go, is triggered by directives in service-specific generate.go files. It parses these directives and their flags to produce a tags_gen.go file within the corresponding service package.
The diagram below illustrates the high-level flow of code generation for tagging functions.
This diagram shows the process from a developer running the generator to the final creation of service-specific tagging code.
Sources: internal/generate/tags/README.md, internal/generate/tags/main.go
Generator Directives and Flags
Code generation is controlled by adding //go:generate directives to a generate.go file within a service's package (e.g., internal/service/waf/generate.go). The main generator at internal/generate/tags/main.go accepts numerous flags to customize the output for a specific service's API.
An example generate.go file:
//go:generate go run ../../generate/tags/main.go -ListTags -ListTagsInIDElem=ResourceARN -ListTagsOutTagsElem=TagInfoForResource.TagList -ServiceTagsSlice -TagInIDElem=ResourceARN -UpdateTags
package waf
Sources: internal/service/waf/generate.go:2
The primary flags to enable function generation are:
| Flag | Description |
|---|---|
GetTag | Generates the GetTag function to fetch a single tag. |
ListTags | Generates the ListTags function to list all tags for a resource. |
UpdateTags | Generates the UpdateTags function to handle tag creation, updates, and deletions. |
ServiceTagsMap | Generates conversion helpers for services using map[string]*string for tags. |
ServiceTagsSlice | Generates conversion helpers for services using a slice of tag structs (e.g., []*service.Tag). |
Sources: internal/generate/tags/main.go:20-25, internal/generate/tags/README.md
Numerous other flags are available to customize element names, input/output structures, and API operation names to match the specific AWS SDK implementation for a service.
Sources: internal/generate/tags/main.go:27-52, internal/generate/tags/README.md
Generated Functions
The code generation framework produces a standard set of functions for each service, providing a consistent API for developers working within the provider.
ListTags
This function lists all tags for a given resource identifier (typically an ARN). It constructs the appropriate input for the service's "list tags" API call (e.g., ListTagsForResource), executes the call, and converts the result into the standard tftags.KeyValueTags type.
Sources: internal/generate/tags/main.go:346-388, internal/generate/tags/README_listtags.md
Example generated ListTags function for worklink:
// ListTags lists worklink service tags.
func ListTags(conn *worklink.WorkLink, identifier string) (tftags.KeyValueTags, error) {
input := &worklink.ListTagsForResourceInput{
ResourceArn: aws.String(identifier),
}
output, err := conn.ListTagsForResource(input)
if err != nil {
return tftags.New(nil), err
}
return KeyValueTags(output.Tags), nil
}
Sources: internal/service/worklink/tags_gen.go:10-26
UpdateTags
The UpdateTags function is the core of tag lifecycle management. It takes the old and new tag maps, calculates the differences, and calls the appropriate AWS SDK methods to add/update tags (TagResource) and remove tags (UntagResource).
The logic flow is as follows:
- Initialize
oldTagsandnewTagsfrom the provided maps. - Calculate tags to be removed by calling
oldTags.Removed(newTags). - If there are tags to remove, construct and send an
UntagResourcerequest. - Calculate tags to be added or updated by calling
oldTags.Updated(newTags). - If there are tags to add/update, construct and send a
TagResourcerequest.
This diagram shows the logical flow within a generated UpdateTags function.
Sources: internal/generate/tags/main.go:731-841, internal/generate/tags/README_updatetags.md
Example generated UpdateTags function for worklink:
// UpdateTags updates worklink service tags.
func UpdateTags(conn *worklink.WorkLink, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error {
oldTags := tftags.New(oldTagsMap)
newTags := tftags.New(newTagsMap)
if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 {
input := &worklink.UntagResourceInput{
ResourceArn: aws.String(identifier),
TagKeys: aws.StringSlice(removedTags.IgnoreAWS().Keys()),
}
// ... call conn.UntagResource(input)
}
if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 {
input := &worklink.TagResourceInput{
ResourceArn: aws.String(identifier),
Tags: Tags(updatedTags.IgnoreAWS()),
}
// ... call conn.TagResource(input)
}
return nil
}
Sources: internal/service/worklink/tags_gen.go:38-68
CreateTags
For some services like EC2, a specialized CreateTags function is generated. This is used to apply tags to a resource immediately after creation. It often includes retry logic to handle eventual consistency issues where a resource might not be found immediately after being created.
Sources: internal/service/ec2/generate/createtags/main.go
Type Conversion Functions
To bridge the gap between the provider's internal tftags.KeyValueTags type and the various AWS SDK tag types, two sets of helper functions are generated:
Tags(tags tftags.KeyValueTags): ConvertsKeyValueTagsinto the service-specific SDK type (e.g.,[]*ec2.Tagormap[string]*string).KeyValueTags(tags ...): Converts the service-specific SDK type back intoKeyValueTags.
These are generated based on the -ServiceTagsSlice or -ServiceTagsMap flags.
Sources: internal/generate/tags/main.go:390-729, internal/generate/tags/README_servicetags.md
aws_..._tag Resource Generation
A separate generator, internal/generate/tagresource/main.go, is responsible for creating standalone Terraform resources that manage a single tag on a parent resource (e.g., aws_ecs_tag). This allows for managing tags independently of the resource they are attached to.
Sources: internal/generate/tagresource/README.md, internal/generate/tagresource/main.go
Generated Resource
The generator creates a tag_gen.go file containing a schema.Resource definition with the following schema:
| Attribute | Type | Description |
|---|---|---|
resource_arn | schema.TypeString | The ARN of the resource to tag. |
key | schema.TypeString | The key of the tag. |
value | schema.TypeString | The value of the tag. |
Sources: internal/generate/tagresource/main.go:88-105
The resource's CRUD functions (resourceTagCreate, resourceTagRead, resourceTagUpdate, resourceTagDelete) are implemented by calling the service-specific GetTag and UpdateTags helper functions that were generated by the main tags generator.
The diagram below shows the sequence for creating an aws_..._tag resource.
This sequence diagram illustrates how creating a tag resource in Terraform translates to an API call to AWS.
Sources: internal/generate/tagresource/main.go:108-121
Conclusion
The tagging implementation in the Terraform AWS Provider is a sophisticated system that prioritizes consistency and developer efficiency. By abstracting away the inconsistencies of AWS service APIs through the KeyValueTags type and a robust code generation framework, it provides a standardized, maintainable, and scalable solution for managing resource tags. This allows developers to add or modify tagging support for services with minimal effort, ensuring a uniform experience across the entire provider.
Sources: internal/generate/tags/README.md, internal/generate/tags/README_keyvaluetags.md