DEV Community

Cover image for Create Terraform Resource References in Go with hclwrite

Create Terraform Resource References in Go with hclwrite

In our last post I introduced the hclwrite library from HashiCorp which allows you to write HashiCorp Configuration Language (HCL) using Go. In that article I created a basic HCL file that defined 150 Business Service resources in a PagerDuty account. Truth be told, I picked Business Services because they didn't require reference values from other resources, because I didn't know how to construct those resource references using hclwrite...until now.

In this post we're going to create HCL that defines a PagerDuty Service. A service requires a reference to a PagerDuty Escalation Policy which, in turn, requires a reference to a PagerDuty Schedule. Each of these references are accomplished by using a resource reference expression. For example, the HCL for defining a service looks something like this, where the escalation_policy attribute is the ID of the escalation policy associated with the service.

resource "pagerduty_service" "me1" {
  name              = "Me Service 1"
  escalation_policy = pagerduty_escalation_policy.ep.id
}
Enter fullscreen mode Exit fullscreen mode

We already know how to create new blocks and set primitive attribute values, as we covered those concepts in the last post. However, we need to first create a service resource block in order to set an escalation_policy attribute. That code for creating the service resource block looks something like this:

service := rootBody.AppendNewBlock("resource", 
    []string{"pagerduty_service", "me1")})
serviceBody := service.Body()
serviceBody.SetAttributeValue("name", cty.StringVal("Me Service 1")))
Enter fullscreen mode Exit fullscreen mode

Now we're ready to add the escalation_policy attribute to serviceBody, which is where things get interesting. To create the name attribute in the code above we used the SetAttributeValue and the cty.StringVal functions to set the string value. But, a resource reference is not a string. It's essentially a variable. If you've been using Terraform for a while you might be thinking you could use the older expression syntax where you set the value as a string and wrapped expression with a ${ }. The hclwrite library actually watches for this syntax and tries to escape it by adding an extra $ to the front, like this $${ }.

There are a couple of approaches that will work for creating these expressions. The first is to use SetAttributeTraversal. A Traversal is designed to define a path of an expression. To define the escalation_policy as a Traversal it would look something like this:

serviceBody.SetAttributeTraversal("escalation_policy", hcl.Traversal{
    hcl.TraverseRoot{
        Name: "pagerduty_escalation_policy",
    },
    hcl.TraverseAttr{
        Name: "ep",
    },
    hcl.TraverseAttr{
        Name: "id",
    },    
})
Enter fullscreen mode Exit fullscreen mode

This creates a perfectly formatted expression of pagerduty_escalation_policy.ep.id.

The other way to create the necessary reference syntax, and the one I prefer, is to use SetAttributeRaw. This function requires you to define the entire expression manually as a series of Tokens. A Token is a struct containing Type and Bytes fields. The Type comes from the hclsyntax.TokenType. And Bytes is a []byte with the value of the Token. In this case, where we're setting the escalation_policy value, the Tokens and subsequent SetAttributeRaw would look something like this:

tokens := hclwrite.Tokens{
  {
    Type: hclsyntax.TokenIdent, 
    Bytes: []byte(`pagerduty_escalation_policy.ep.id`),
  },
}

serviceBody.SetAttributeRaw("escalation_policy", tokens)
Enter fullscreen mode Exit fullscreen mode

There are a few reasons why I prefer using the SetAttributeRaw method. The first advantage you'll notice from this approach is that it takes less code than the Traversal. However, the real advantage comes when creating attributes that contain a list value. We do just that when defining a pagerduty_schedule resource. In the layer block of the schedule resource we set a list of users.

The HCL for the entire PagerDuty Schedule looks something like this:

resource "pagerduty_schedule" "foo" {
  name      = "Me Schedule"
  time_zone = "America/Los_Angeles"

  layer {
    name                         = "This Shift"
    start                        = "2022-03-15T20:00:00-08:00"
    rotation_virtual_start       = "2022-03-15T20:00:00-08:00"
    rotation_turn_length_seconds = 86400
    users                        = [data.pagerduty_user.me.id]

    restriction {
      type              = "daily_restriction"
      start_time_of_day = "17:00:00"
      duration_seconds  = 54000
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The Go code needed to create this pagerduty_schedule resource and layer blocks looks like this:

// create schedule
sched := rootBody.AppendNewBlock("resource",
  []string{"pagerduty_schedule", "foo"})
schedBody := sched.Body()
schedBody.SetAttributeValue("name", cty.StringVal("Me Schedule"))
schedBody.SetAttributeValue("time_zone", cty.StringVal("America/Los_Angeles"))
schedBody.AppendNewline()
schedLayer := schedBody.AppendNewBlock("layer", nil)
schedLayerBody := schedLayer.Body()
schedLayerBody.SetAttributeValue("name", cty.StringVal("This Shift"))
schedLayerBody.SetAttributeValue("start", cty.StringVal("2022-03-15T20:00:00-08:00"))
schedLayerBody.SetAttributeValue("rotation_virtual_start", cty.StringVal("2022-03-15T20:00:00-08:00"))
Enter fullscreen mode Exit fullscreen mode

Creating the Tokens for the users list looks extremely similar to the code we used above to create the escalation_policy reference. The difference now is that we wrap our data.pagerduty_user.me.id value in opening and closing square brackets, like so:

userTokens := hclwrite.Tokens{
  {
    Type:  hclsyntax.TokenOBrack,
    Bytes: []byte(`[`),
  },
  {
    Type:  hclsyntax.TokenIdent,
    Bytes: []byte(`data.pagerduty_user.me.id`),
  },
  {
    Type:  hclsyntax.TokenCBrack,
    Bytes: []byte(`]`),
  },
}

schedLayerBody.SetAttributeRaw("users", userTokens)
Enter fullscreen mode Exit fullscreen mode

Hopefully, you now have a better idea of how to use the SetAttributeRaw function when writing resource references in HCL when using the hclwrite library. You can find all the code that was described in this article in the Create Terraform Files in Go repository on GitHub.

Discussion (0)