Part 2: Validating AWS Tags with Terraform

Author Derek Morgan

Last updated 2 May, 2023

4 mins read

Part 2: Validating AWS Tags with Terraform150

Note: this is a continuation of Basics of AWS Tags & Terraform with S3. If you’d like to skip it, you can obtain the starter code repository on Github here as this is written to be followed along.

In Part 1: Basics of AWS Tags & Terraform with S3 blog post, we saw that adding tags to resources is fairly straightforward. Using tag blocks is trivial, but very repetitive, and utilizing default_tags allows us to specify tags for entire deployments. Those methods work well, but how do we go about validating those tags? What if someone spells a tag incorrectly, misses the capitalization, or just forgets the tag altogether? That’s where tag validation comes into play!

Refactor our Terraform Deployment into Modules

First, let’s refactor our deployment. This will give us more flexibility over the tags and other attributes that are assigned to our modules.

Create main.tf within a new directory called s3-bucket

Within s3-bucket/main.tf, add the following code (and remove the same resources from the original main.tf). Notice the tags attribute is now defined as var.tags, which we haven’t initialized yet. We have also modified the bucket attribute to create a prefix dynamically based on the definition of the team tag.





Replace the existing buckets in main.tf with module references





1
2
3
4
5
6
7
8
resource "random_id" "s3_id" {
    byte_length = 2
}

resource "aws_s3_bucket" "team-bucket" {
    bucket = "${var.tags["team"]}-bucket-${random_id.s3_id.dec}"
    tags = var.tags
}

After you’ve added the code, run a terraform init to re-initialize the deployment for the new modules.

AWS Tags Validation

Now, what we want to do is validate that each resource has the team and service tags. We already know they all have the env tag because it’s a default, but the other resources are up to the engineers to tag. Let’s utilize terraform console and some Terraform functions to see how we can validate that these tags exist.

Let’s create a variables.tf file in our root directory and add the following code:





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module "finance-bucket" {
    count  = 1
    source = "./s3-bucket"
    tags = {
        team    = "finance"
        service = "s3"
    }
}

module "dev-bucket" {
    count  = 1
    source = "./s3-bucket"
    tags = {
        team    = "devops"
        service = "s3"
    }

Now that this has been created, let’s access terraform console and run a few commands that will help us understand how the validation condition above works.

First, since we’re trying to validate that the keys in our tags, “team” and “service”, exist, let’s use the keys() Terraform function on our tags block:





1
2
3
4
5
6
7
variable "tags" {
    type = map
    validation {
    condition = alltrue([for t in ["team", "service"] : contains(keys(var.tags), t)])
    error_message = "Please include tags for team and service."
    }
}

Perfect! Now we have our keys. The next step is to confirm that they are both included. To do this, let’s first see how we would validate that one of our keys is included. We’ll validate that the “service” key is included in a list by using the contains() Terraform function:





1
2
3
4
5
> keys({service = "s3", team = "devops"})
[
  "service",
  "team",
]

Excellent, so we’ve validated that one of our keys exists, so let’s use a Terraform Loop to validate this. I’ll do this in stages so you can see the steps:

First, we’ll run a basic for loop to see our keys:





1
2
> contains(["service", "team"], "service")
true

Then, we’ll use the contains() function to test each item in the loop:





1
2
3
4
5
6
> [for t in ["env", "team", "dev"] : t]
[
  "env",
  "team",
  "dev",
]

Finally, we can utilize the alltrue() function to get one true value:





1
2
3
4
5
6
> [for t in ["env", "team", "service"] : contains(keys({env = "dev", team = "Finance", service = "S3"}), t)]
[
  true,
  true,
  true,
]

Awesome! We can now use this function to validate our tags.

Go ahead and exit the Terraform Console and then run a terraform apply -auto-approve.

Assuming that went well, (it should have), let’s now see if we can break it!
Comment out the “service” tag of dev-bucket:





1
2
> alltrue([for t in ["env", "team", "service"] : contains(keys({env = "dev", team = "Finance", service = "S3"}), t)])
true

Run another terraform apply -auto-approve:





1
2
3
4
5
6
7
8
module "dev-bucket" {
  count  = 1
  source = "./s3-bucket"
  tags = {
    team    = "devops"
    # service = "s3"
  }
}

Boom! That worked exactly as we had hoped! You can also try misspelling each tag, changing caps, whatever you want. Everything should be caught properly!

Conclusion

Alright, so that’s all for this installment, go ahead and run a terraform destroy -auto-approve and join me on the next one as we dive deeper into this deployment and create more robust tag validation code!

Author Derek Morgan
Industrial IoT engineer and course creator for More Than Certified. His Terraform course on Udemy has over 10,000+ students to date.

Manage, track, and report your AWS spending in seconds — not hours

CloudForecast’s focused daily AWS cost monitoring reports to help busy engineering teams understand their AWS costs, rapidly respond to any overspends, and promote opportunities to save costs.

Monitor & Manage AWS Cost in Seconds — Not Hours

CloudForecast makes the tedious work of AWS cost monitoring less tedious.

AWS cost management is easy with CloudForecast

We would love to learn more about the problems you are facing around AWS cost. Connect with us directly and we’ll schedule a time to chat!

AWS daily cost reports
1
2
3
4
5
6
7
8
9
10
11
│ Error: Invalid value for variable
│ 
│   on main.tf line 35, in module "dev-bucket":
│   35:   tags = {
│   36:     team    = "devops"
│   37:     service = "s3"
│   38:   }
│ 
│ Please specify the team and service tags.
│ 
│ This was checked by the validation rule at s3-bucket/variables.tf:3,5-15.