Tags in Terraform, overriding defaults and module-level tags

A pattern for specifying module tags that can be overrided, with default tags.

Table Of Contents

Today I Explained

The Terraform AWS Provider supports a field known as default_tags which can significantly cutdown on the amount of copying & pasting when it comes to apply tags on all resources within a deployment. This is especially useful for Terraform modules, as they don’t require passing along tags to each sub-module contained within. This makes the overhead of setting up metadata within a Terraform module more practical, allowing for things like licensing, documentation URL or compliance-driven tags to be defined inside of the module itself.

An issue that arises with embedding these tags in the modules, is that consumers of Terraform modules usually need some flexibility with specifying tags to the module. As these tags are often used for cost-centre or compliance resources within an organization, and thus mandated to exist on the deployed resources.

To address this, one can make use of the merge function, which allows the module to define it owns list of tags, that can be overwritten by the consumer of the module as needed. This involves creating a tags variable, which can have values passed in by the consumer, and a local known as module_tags. The resultant merging of these two fields yields a local called tags that can be passed in to the Terraform provider.

# providers.tf
provider "aws" {
    default_tags {
        tags = local.tags
    }
}

# tags.tf
variable "tags" {
    default = {
        # ...
    }
}

locals {
    module_tags = {
        ...
        "documentation-url": "https://..."
    }
    tags = merge(local.module_tags, var.tags)
}

Any module tag specified to the tags variable will be overwritten by the value provided by the consumer. If desired, the module can even implement logic to prefix all tags provided by the module to avoid potential naming conflicts in the conventions. This then allows any consumer significant flexibility over the tags, while still respecting the opinionated approach of the Terraform module.

terraform plan -var tags="{..}"

A note on modules within modules

One of the challenges that can come up with module tags, is that if a common specification is leveraged, such as documentation-url, the usage of re-usable component within a Terraform module can see the overriding of the common label (documentation-url) with the root Terraform module.

[+] terraform-aws-stepfunctions-solutions
 │     (documentation-url: https://stepfunctions-are-us)
 └────> modules/terraform-aws-lambda-solution
            (documentation-url: https://lambdas-are-us)

Although in these cases it may be desired to have the root Terraform module be the sole source of the module tags, in other cases it may not be. For these kinds of cases, considerations can be made for supporting the ability to change the prefix of the sub-module tags, or adding variables that enable functionality to respecting the sub-modules tags.

A note on preventing overrides

You can also prevent overrides by rejecting any tag key that already is defined by the Terraform module, using the variable validation options made available within Terraform. Alternatively it can be done by comparing the actual key count of the merged tag dictionary, with the count of the tags and modules_tags values.

Although often just reserving the tag keys and discouraging certain usages is sufficient, and then if the consumer wishes, they can override it.