Skip to main content

Tagging Resources

Most companies have a tagging policy: an owner, a cost centre, an environment, on everything. The usual way to satisfy it is for every module to accept a tags variable and every caller to pass the same map, which means the policy is enforced by convention across every file that creates anything, and a new module is compliant only if its author remembered.

Octo applies tags from outside the modules. You declare them once in octo.yaml, with a scope, and every resource a run produces picks them up. Module authors write nothing.

The scopes​

tags is a list, and each entry is a set of tags together with the scope they reach.

octo.yaml
tags:
# Every resource in the run.
- tags:
ManagedBy: octo
Owner: platform-team

# Every resource the region module produced.
- scope:
moduleId: region-module
tags:
Tier: network

scope is optional, and leaving it out means the same as writing scope: {}: the tags reach everything. A moduleId narrows them to the resources that one module declared, and where both set the same key, the narrower scope wins.

A key belongs to one entry per scope. Two global entries that both set Owner stop the run before anything is written:

Error: Tag has already been registered!
inputKey: Owner

A scope naming a module that is not in modules fails the same way, with Module not found! and the ID you typed. Tag values are strings, so an unquoted true or a number is caught earlier still, by the schema:

  [/tags/1/tags/Critical] must be string

A value may come from env like any other, as "${env.COST_CENTRE}", which is how one file carries a different cost centre per deployment.

Where the tags end up​

Octo resolves the tags for every resource before the Terraform is written, so the two entries above reach the shop's VPC as one map:

.octo-tf/region-module/main.tf
resource "aws_vpc" "vpc-eu-west-1" {
provider = aws._123456789012-eu-west-1
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
instance_tenancy = "default"
tags = {
ManagedBy = "octo"
Owner = "platform-team"
Tier = "network"
}
}

The receipts bucket sits in a different module, so it carries the two global tags and not Tier:

.octo-tf/receipts-module/main.tf
resource "aws_s3_bucket" "bucket-shop-receipts" {
provider = aws._123456789012-eu-west-1
bucket = "shop-receipts"
tags = {
ManagedBy = "octo"
Owner = "platform-team"
}
}

Every resource in @quadnix/octo-aws-cdk that Terraform creates carries them, so a tag you declare reaches the whole stack. A resource of your own opts in with two lines, which Create a Terraform Resource shows.

Tags are part of the diff, so adding one to a live stack shows up in the next report as an update on each resource that gains it, rather than as an invisible change.

What to tag with​

Two things are worth declaring globally on any real project, and neither is about cost reporting.

An owner, so that whoever finds an unexpected resource in the console knows who to ask. And a marker saying the resource is managed by Octo, so that a resource created by hand is distinguishable from one this project is responsible for. The second one pays for itself the first time somebody asks whether a bucket is safe to delete.