Skip to content

PlatformStackPulse/tf-label

Repository files navigation

tf-label

Terraform Format Terraform Validate TFLint Terraform Test Security Scan Conventional Commits Documentation License

Terraform module to define a consistent naming convention by (namespace, tenant, environment, stage, name, [attributes]).

A modern rewrite of cloudposse/terraform-null-label, updated for Terraform 1.5+ with proper type safety, simplified logic, and removal of deprecated patterns.

Features

  • Generates a consistent id from configurable label elements
  • Produces a tags map for AWS resources with label values
  • Supports context chaining between modules (parent → child inheritance)
  • Configurable case transformation (lower, upper, title, none)
  • ID length limiting with sha256-based hash suffix for uniqueness
  • Custom descriptor_formats for alternative output strings
  • Full type safety via optional() object types (no more type = any)

Requirements

Name Version
terraform >= 1.5.0

Usage

module "label" {
  source = "git::https://github.com/PlatformStackPulse/tf-label.git?ref=v1.0.0"

  namespace   = "eg"
  environment = "us-west-2"
  stage       = "prod"
  name        = "app"
  attributes  = ["public"]

  tags = {
    BusinessUnit = "Engineering"
  }
}

resource "aws_s3_bucket" "example" {
  bucket = module.label.id   # => "eg-us-west-2-prod-app-public"
  tags   = module.label.tags
}

Context Chaining

Pass a parent label's context to child modules to inherit all settings:

module "parent" {
  source    = "git::https://github.com/PlatformStackPulse/tf-label.git?ref=v1.0.0"
  namespace = "acme"
  stage     = "prod"
  name      = "platform"
}

module "child" {
  source  = "git::https://github.com/PlatformStackPulse/tf-label.git?ref=v1.0.0"
  name    = "worker"
  context = module.parent.context
}
# child.id => "acme-prod-worker"

Custom Label Order

module "label" {
  source      = "git::https://github.com/PlatformStackPulse/tf-label.git?ref=v1.0.0"
  namespace   = "eg"
  stage       = "prod"
  name        = "app"
  label_order = ["name", "namespace", "stage"]
}
# label.id => "app-eg-prod"

Pascal Case (No Delimiter)

module "label" {
  source           = "git::https://github.com/PlatformStackPulse/tf-label.git?ref=v1.0.0"
  namespace        = "eg"
  stage            = "prod"
  name             = "myapp"
  delimiter        = ""
  label_value_case = "title"
}
# label.id => "EgProdMyapp"

ID Length Limit

module "label" {
  source          = "git::https://github.com/PlatformStackPulse/tf-label.git?ref=v1.0.0"
  namespace       = "acme"
  environment     = "production"
  name            = "my-long-service-name"
  id_length_limit = 32
}
# label.id => truncated with sha256 hash suffix for uniqueness

Descriptor Formats

module "label" {
  source    = "git::https://github.com/PlatformStackPulse/tf-label.git?ref=v1.0.0"
  namespace = "acme"
  stage     = "prod"
  name      = "api"

  descriptor_formats = {
    stack = {
      format = "%s/%s/%s"
      labels = ["namespace", "environment", "stage"]
    }
    qualified_name = {
      format = "%s::%s"
      labels = ["namespace", "name"]
    }
  }
}
# module.label.descriptors["stack"]          => "acme//prod"
# module.label.descriptors["qualified_name"] => "acme::api"

Requirements

Name Version
terraform >= 1.5.0

Providers

No providers.

Modules

No modules.

Resources

No resources.

Inputs

Name Description Type Default Required
attributes ID element. Additional attributes (e.g. workers or cluster) to add to id,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the delimiter
and treated as a single ID element.
list(string) [] no
context Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as null to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes and tags, which are merged.
object({
enabled = optional(bool, true)
namespace = optional(string, null)
tenant = optional(string, null)
environment = optional(string, null)
stage = optional(string, null)
name = optional(string, null)
delimiter = optional(string, null)
attributes = optional(list(string), [])
tags = optional(map(string), {})
label_order = optional(list(string), null)
regex_replace_chars = optional(string, null)
id_length_limit = optional(number, null)
label_key_case = optional(string, null)
label_value_case = optional(string, null)
labels_as_tags = optional(set(string), null)
descriptor_formats = optional(map(object({
format = string
labels = list(string)
})), {})
})
{} no
delimiter Delimiter to be used between ID elements.
Defaults to - (hyphen). Set to "" to use no delimiter at all.
string null no
descriptor_formats Describe additional descriptors to be output in the descriptors output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
{<br/> format = string<br/> labels = list(string)<br/>}
format is a Terraform format string to be passed to the format() function.
labels is a list of labels, in order, to pass to format() function.
Label values will be normalized before being passed to format() so they will be
identical to how they appear in id.
Default is {} (descriptors output will be empty).
map(object({
format = string
labels = list(string)
}))
{} no
enabled Set to false to prevent the module from creating any resources. bool null no
environment ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'. string null no
id_length_limit Limit id to this many characters (minimum 6).
Set to 0 for unlimited length.
Set to null to keep the existing setting, which defaults to 0.
Does not affect id_full.
number null no
label_key_case Controls the letter case of the tags keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the tags input.
Possible values: lower, title, upper.
Default value: title.
string null no
label_order The order in which the labels (ID elements) appear in the id.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present.
list(string) null no
label_value_case Controls the letter case of ID elements (labels) as included in id,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the tags input.
Possible values: lower, title, upper and none (no transformation).
Set this to title and set delimiter to "" to yield Pascal Case IDs.
Default value: lower.
string null no
labels_as_tags Set of labels (ID elements) to include as tags in the tags output.
Default is to include all labels.
Tags with empty values will not be included in the tags output.
Set to [] to suppress all generated tags.
Note: The value of the name tag, if included, will be the id, not the name.
set(string) null no
name ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a tag.
The "name" tag is set to the full id string. There is no tag with the value of the name input.
string null no
namespace ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique. string null no
regex_replace_chars Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, "/[^a-zA-Z0-9-]/" is used to remove all characters other than hyphens, letters and digits.
string null no
stage ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'. string null no
tags Additional tags (e.g. {'BusinessUnit': 'XYZ'}).
Neither the tag keys nor the tag values will be modified by this module.
map(string) {} no
tenant ID element. A customer identifier, indicating who this instance of a resource is for. string null no

Outputs

Name Description
attributes List of normalized attributes.
context Merged but otherwise unmodified input to this module, to be used as context input to other modules.
Note: this version will have null values as defaults, not the values actually used as defaults.
delimiter Delimiter between ID elements.
descriptors Map of descriptors as configured by descriptor_formats.
enabled True if module is enabled, false otherwise.
environment Normalized environment.
id Disambiguated ID string restricted to id_length_limit characters in total.
id_full ID string not restricted in length.
id_length_limit The id_length_limit actually used to create the ID, with 0 meaning unlimited.
label_order The naming order actually used to create the ID.
name Normalized name.
namespace Normalized namespace.
normalized_context Normalized context of this module.
regex_replace_chars The regex_replace_chars actually used to create the ID.
stage Normalized stage.
tags Normalized Tag map.
tenant Normalized tenant.

Using exports/context.tf

Copy exports/context.tf into downstream modules to automatically expose the context variable with the correct type definition. This ensures consistent context passing throughout your module hierarchy.

Development

make dev-setup   # Install pre-commit hooks
make fmt         # Format code
make validate    # Run terraform validate
make lint        # Run TFLint
make test        # Run unit tests (19 scenarios)
make security    # Trivy IaC scan
make all         # Full CI pipeline locally

Tests

19 unit test scenarios covering:

  • Basic ID generation and full label composition
  • Tag generation and selective labels_as_tags
  • Custom delimiters and label ordering
  • Case transformations (upper, pascal, none)
  • ID length truncation with hash suffix
  • Disabled module (empty outputs)
  • Regex character replacement
  • Context chaining between modules
  • Tag key case control
  • Tenant in label order
  • Descriptor formats

Run tests:

terraform init -backend=false
terraform test

Migration from cloudposse/terraform-null-label

What Changed

Area Old (terraform-null-label) New (tf-label)
Terraform version >= 0.13.0 >= 1.5.0
Context variable type type = any (no type safety) object() with optional() — full type checking
Sentinel values ["unset"] / ["default"] workarounds Native null — Terraform 1.0 fixed the underlying bug
tags_as_list_of_maps Present (for old ASG tag format) Removed — AWS provider 4.0+ uses standard tags = {}
additional_tag_map Present (for ASG propagation) Removed — no longer needed
Hash algorithm md5() sha256() — stronger collision resistance
descriptor_formats type type = any Properly typed map(object({format, labels}))
label_order validation None Validates entries are valid label names
labels_as_tags default Sentinel ["default"] with magic detection null = include all labels (idiomatic)
Null handling Complex lookup() + try() + contains() Direct field access on typed object
Code complexity ~170 LOC with workaround comments ~140 LOC clean logic

Breaking Changes

  1. tags_as_list_of_maps output removed — If you used this for AWS autoscaling group tags, switch to the standard tags = {} syntax (supported since AWS provider 4.0).

  2. additional_tag_map input removed — Merge any additional tags directly into the tags input.

  3. labels_as_tags default — Use null (include all) instead of ["default"].

  4. Hash suffix differs — If you use id_length_limit and the ID exceeds the limit, the 5-character hash suffix uses sha256 instead of md5. This means truncated IDs will differ from the old module. If you don't use id_length_limit, there is no difference.

Test Comparison

Both modules produce identical output for all 19 test scenarios (same inputs, same expected outputs) except for the hash suffix when id_length_limit triggers truncation:

Old (md5):    acme-production-re-b96f7
New (sha256): acme-production-re-2bbf9

License

MIT — see LICENSE

About

tf-label is Terraform module to define a consistent naming convention by (namespace, tenant, environment, stage, name, [attributes]). A modern rewrite of cloudposse/terraform-null-label, updated for Terraform 1.5+ with proper type safety, simplified logic, and removal of deprecated patterns.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors