Is there a way to avoid hardcoding Terraform TypeSet hashed index values in provider tests?

Viewed 40

When using TypeSet, Terraform indicates the hash indexes are calculated based on the values of the set. I have a Terraform Provider written in Go using the Terraform Provider Go SDK where I currently need to hard code these index values in the tests. Can hardcoding the values in the Terraform Provider tests be avoided?

Terraform docs say the following, but don't provide information on how to calculate the hash index values or avoid calculating them.

TypeSet items are stored in state with an index value calculated by the hash of the attributes of the set.

Ref: Terraform docs: https://www.terraform.io/plugin/sdkv2/schemas/schema-types

Here's the example provided in the documentation where 1061987227 and 493694946 are auto-generated hash index values.

"ingress.#": "2",
"ingress.1061987227.cidr_blocks.#": "1",
"ingress.1061987227.cidr_blocks.0": "10.0.0.0/8",
"ingress.1061987227.description": "",
"ingress.1061987227.from_port": "80",
"ingress.1061987227.ipv6_cidr_blocks.#": "0",
"ingress.1061987227.protocol": "tcp",
"ingress.1061987227.security_groups.#": "0",
"ingress.1061987227.self": "false",
"ingress.1061987227.to_port": "9000",
"ingress.493694946.cidr_blocks.#": "2",
"ingress.493694946.cidr_blocks.0": "0.0.0.0/0",
"ingress.493694946.cidr_blocks.1": "10.0.0.0/8",
"ingress.493694946.description": "",
"ingress.493694946.from_port": "80",
"ingress.493694946.ipv6_cidr_blocks.#": "0",
"ingress.493694946.protocol": "tcp",
"ingress.493694946.security_groups.#": "0",
"ingress.493694946.self": "false",
"ingress.493694946.to_port": "8000",

Currently, in my Go tests, I hardcode the hash index values which I'd like to avoid. Here's an excerpt with 3 hard coded index values: 368698502, 1552086545, and 3172309932:

resource.TestCheckResourceAttr(resourceName, "workspace_configs.368698502.client_ip_header", "Fastly-Client-IP"),
resource.TestCheckResourceAttr(resourceName, "workspace_configs.368698502.instance_location", "advanced"),
resource.TestCheckResourceAttr(resourceName, "workspace_configs.368698502.listener_protocols.#", "1"),
resource.TestCheckResourceAttr(resourceName, "workspace_configs.368698502.listener_protocols.1552086545", "https"),
resource.TestCheckResourceAttr(resourceName, "workspace_configs.368698502.routes.#", "1"),
resource.TestCheckResourceAttr(resourceName, "workspace_configs.368698502.routes.3172309932.certificate_ids.#", "0"),
resource.TestCheckResourceAttr(resourceName, "workspace_configs.368698502.routes.3172309932.connection_pooling", "true"),

The full code is here: https://github.com/signalsciences/terraform-provider-sigsci/blob/main/provider/resource_corp_cloudwaf_instance_test.go

Is there a way to avoid hard coding the index values? For example, is it possible to create a function that fulfills the CheckResourceAttrWithFunc interface used with TestCheckResourceAttrWith?

1 Answers

These hash values are a holdover from older versions of Terraform which SDKv2 continues to use so that providers built with it can remain compatible with those older versions of Terraform.

resource.TextCheckResourceAttr is a helper function which returns a resource.TestCheckFunc value, which is really just a named type for a reference to a function with the following signature:

func(*terraform.State) error

There are many other helper functions which return test check functions in the resource package, and some of them are specifically intended for describing assertions about sets while avoiding mentioning a specific hash key:

I would suggest first reviewing the documentation for each of those to see if it matches the kind of assertion you'd like to write.

If you find that none of them matches the rule you need to describe then the fallback is to write your own function matching the above signature, which can then use arbitrary logic to analyze the data in the given *terraform.State value.

You can use the predefined test check function helpers as examples for how to work with that API. Although there's some indirection in how it's implemented in the resource package, the general pattern is:

  1. Access the root module (since provider tests don't typically use nested modules)

    ms := s.RootModule()
    
  2. Find the specific resource instance inside the root module that you want to test against:

    rs, ok := ms.Resources[name]
    if !ok {
        return nil, fmt.Errorf("Not found: %s in %s", name, ms.Path)
    }
    
    is := rs.Primary
    if is == nil {
        return nil, fmt.Errorf("No primary instance: %s in %s", name, ms.Path)
    }
    
  3. is is a pointer to a terraform.InstanceState object, whose attributes are in the Attributes map.

    You'll find in here the raw keys including the hash values that you want to avoid hard-coding, so you'll typically need to write logic that iterates over all of the attributes to look for ones matching a particular prefix, then possibly also match a particular suffix to determine which leaf attribute you've found, and then make assertions only for values in the map whose keys match the relevant criteria.

Related