Terraform, Timestamps, and Tagging

One of the key properties missing from Azure resources, in my opinion anyway, is a CreatedDate. This can be largely overcomes with Azure policy, but what if you don’t have access to create one that applies a timestamp tag at resource creation?

It is possible to use Terraform to tag the resource and set the value for when the resource is created. There is a little more work that needs to go into it to ensure that once it is set that Terraform does not overwrite it on subsequent deployments. But, it is achievable and brings this into your control if needed.

Code

All of the code samples needed for this post can be found in my GitHub repository here.

Why would I want this?

Ask yourself this, have you ever wondered when resources were created or tried to track down a spike in cost due to resources being created? You can go trawling through cost management if you have the required access, or we can add it to all our resources at deployment so that we can look back later.

How to achieve this?

There are several great little functions available to us in HCL with Terraform, but the one we are interested in today is timestamp.

The timestamp function in Terraform will return the UTC date and time that it is called when we perform an apply or plan operation. The advantage of using UTC is the consistency no matter where in the world our team is operating. We don’t need to track which time zone the deployment happened in to then try and map that to a specific time and date in our time zone. All we have to do is work out our offset from UTC if needed.

The other key benefit of this function is that it will provide the date in ISO 8601 format so there is consistency whether the deployment is made in the US, or the rest of the world. While it is possible to use the formatdate function to alter this formatting, I would strongly recommend that the default is used for consistency.

Writing The HCL

The structure of the project follows my pattern of breaking out tags and provider details into their own files. The main.tf will contain simple a resource group which will be used to illustrate the capabilities.

Defining Our Tags

Before we can create the resource we really ought to define our tags which we will apply to it. I’m a big fan of defining a default set of tags in a locals block so that we can reference it easily, it also gives us a bit more versatility in adding resource specific options if we need to.

In the tags.tf file we define our locals with a common_tags block which lists our tags.

locals {
  common_tags = {
    Environment = var.Environment
    Contact     = var.Contact
    CostCenter  = var.CostCentre
    Usage       = var.Usage
    CreatedDate = timestamp()
  }
}

Here we can see that Environment, Contact, CostCentre and Usage are all provided using variables. This allows us to define them at resource creation time and gives us the ability to reuse our code more effectively. The CreatedDate tag however is set to use timestamp() to obtain its value at execution time. Now with this defined we can move on to setting up our resource.

Defining our Resource

When we define our resource we will need to provide the core attributes needed in order to deploy it. Once those are defined we can then add our tags reference and we are in a position to deploy our resource.

resource "azurerm_resource_group" "Demo" {
  name     = var.ResourceGroupName
  location = var.ResourceGroupLocation

  tags = merge(local.common_tags)
}

However, if we deploy this then every time we do any subsequent deployment the CreatedDate tag will be updated. This is because the value for timestamp will be different to the one which exists in the resource tag.

So, how do we get around this?

We can use a lifecycle block and tell Terraform that on apply operations it should ignore any difference between the current value for the CreatedDate tag and the value generated as part of the apply operation. This way the value that is used when we create a resource will not be updated by subsequent apply operations. The lifecycle definition is provided below.

  lifecycle {
    ignore_changes = [
      tags["CreatedDate"]
    ]
  }

This block is placed within the resource definition (see the complete main.tf in the GitHub repo) and is needed for each and every resource which we use this tag for. That way it becomes part of the standard definition of our resources. As can be seen below, the deployment takes place then a subsequent deployment will make no changes because it is excluding the CreatedDate tag.

Terraform apply and then another apply showing that resource is created and the CreatedDate tag is excluded from evaluation by Terraform.
Figure 1. Terraform applying resources but not updating CreatedDate tag.

From here it is then possible to think about adding a LastUpdated tag which uses the same process for assigning a timestamp. Bu by not including it in the lifecycle block so that it will be updated every time we run an apply operation.

Summary

Here we walked through one possible way of tracking when Azure resources are created. Hopefully this is of some use to you as you manage your environments and helps track down what is created when. It would be great to understand how you are handling this at the moment and if there are alternatives that work for you. Feel free to let me know in the comments below.

2 thoughts on “Terraform, Timestamps, and Tagging

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.