Skip to content
Go back

Inside Terraform: addrs - Everything Needs an Address

Disclaimer: I am working at HashiCorp (now IBM) as part of the Terraform Core team. The postings on this site are my own and don’t necessarily represent IBM’s positions, strategies or opinions.
Since I am involved in Terraform my opinions can sometimes be (unconsciously) biased. I hope you enjoy the post anyway.

This is part of my Inside Terraform series where I deep dive into different parts of Terraform and explain how they work under the hood. This is the first post in the series, so you found the right place to start!

I’m still experimenting with the format of these posts, so please let me know if you have feedback or suggestions for improvement.

Content

What is addrs?

The addrs package contains structs to represent addresses of different Terraform entities: Resources, Data Sources, Modules, Outputs, etc. These addresses come in different shapes / contexts: Relative addresses, Config Addresses, Absolute Addresses, etc. For expandable entities (the ones that support the count / for_each meta-argument) we also have instance addresses.

Most entities have an address struct per context associated with them and maybe even an instance address (e.g. addrs.AbsResourceInstance).

Aside from the string representation of addresses (e.g. module.foo.aws_instance.bar[0]) these address structs have methods to convert them into other address types and implement certain marker interfaces (e.g. Referencable).

How to addrs

I’m using resources as an example here, but the same applies for all other entities in Terraform as well.

The address structs are used as (unique) identifiers for entities within Terraform. I always think of them as the names of the entities that exist. When we read a configuration file, we assign each entity an address of the corresponding type. The configuration file belongs to a module (either to the root module - your top-level config you ran terraform ... on or to a child module you referenced in a module call).

// Example resource "aws_instance" "example" {}
entity := addrs.Resource{
    Mode: addrs.ManagedResourceMode, // A resource and not e.g. a data source
    Type: "aws_instance",
    Name: "example",
}
currentModule := addrs.RootModule{} // or a child module address
configAddr := entity.InModule(currentModule) 
// -> addrs.ConfigResource{Module, Resource}

This means we can now make the entity address a Config<Entity> type which holds the module we are in. This way we can tell apart two resources with the same type and name that live in different modules.

At a later point we will need to deal with the count or for_each meta-argument of the module call. This means we don’t want a ConfigResource but an AbsResource that instead of an addrs.Module holds an addrs.ModuleInstance. The instance refers to the “expanded” module and even if no count / for_each is used, we still have an instance address with addrs.NoKey.

Lastly we need to expand the resource itself into an instance address, moving Resource to ResourceInstance. If we now combine the module instance and the resource instance, we get the full absolute address of the resource instance (AbsResourceInstance). This is the most specific address type we have for resources.

// Continuation of the previous example
moduleInstance := addrs.RootModuleInstance // or a child module instance address
instanceAddr := entity.Instance(addrs.StringKey("foo"))
// -> ResourceInstance{Resource, InstanceKey}

absResourceInstance := instanceAddr.Absolute(moduleInstance)
// -> AbsResourceInstance{ModuleInstance, ResourceInstance}

absResource := entity.Absolute(moduleInstance)
// -> AbsResource{ModuleInstance, Resource}

absResourceInstance2 := absResource.Instance(addrs.StringKey("foo"))
// -> AbsResourceInstance{ModuleInstance, ResourceInstance}

Why do we need addrs?

Aside from being super useful when e.g. printing the graph or debugging in general, the address structs are used all over the place in Terraform to identify entities.

A prominent example is the way we deal with references. A graph node can implement a function ReferenceableAddrs that specifies which addresses it can be referenced by and it can implement References to specify which other entities it references. Both together are used to make the connections in the dependency graph when you e.g. use aws_instance.example somewhere in your configuration.

Since all addresses have a string representation we can also parse addresses from strings. This is for example used when you run terraform plan -target aws_instance.example - we parse the string into an address struct and then find the corresponding entity in the graph to filter the rest out.


Share this post on:




Next Post
Inside Terraform: A series about the internals of Terraform