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
}
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")))
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",
},
})
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)
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
}
}
}
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"))
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)
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.
Top comments (0)