Verify the shape of terraform output #678
-
Hi Forum. I have pretty much hit a block on this. I am very much in the process of learning go and terratest 😺 I wanted to test terraform output, as it often the thing I find problematic when passing output to other loops though locals and failing on missing indexes/ attributes and so on. I have looked at this example which looked pretty straightforward but rather than test for a string or absolute value I was looking to generalise for a type and potentially with a view to move on to perhaps writing functions later to test the value, e.g. regex or integer ranges and so on. Struct looked highly suitable - arguably perfect for what I am trying to achieve in my first step - so much perhaps, this may have me in a rabbit hole: type NsgEntry struct {
access string
destination_address_prefix string
destination_port_range []string
direction string
name string
priority int
protocol string
source_address_prefix string
source_port_range string
} I was naively hoping the output from terraform (which in my case creates a list of logical objects) could be compared against this struct and verified to have the right 'shape'. For instance in the loop at the bottom here: func TestTerraformNsgMap(t *testing.T) {
t.Parallel()
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../examples/terraform-nsg-map-example",
VarFiles: []string{"nsg.tfvars"},
NoColor: false,
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
actualTfOutput := terraform.OutputMap(t, terraformOptions, "nsg")
for _, value := range actualTfOutput {
assert.IsType(t, value, NsgEntry)
}
} The assert is where I run into problems. Also the way that terratest is 'presenting the output' to go is confusing me as it is almost as it if requires unmarshalling from JSON, despite the OutputMap method being used. I have written more go using the struct directly (based on a book I am reading) and the behaviour of go is very different. In this instance I can access attributes of the object created from the struct like I would do in a python object, which is more familiar to me. I have searched for similar use cases, but I haven't really found anything suitable which is making wonder if my approach if fundamentally wrong. Would very much appreciate any guidance on the above if possible. Thanks in advance. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 5 replies
-
Hello @niksheridan! It seems like your approach to use a struct to represent the expected shape of Terraform output is reasonable. I believe the issue you are encountering is likely due to how Terraform output is returned as type In your test function, you're using the |
Beta Was this translation helpful? Give feedback.
-
Final thing package tests
import (
"fmt"
"reflect"
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
)
type NsgEntry struct {
Access string
Destination_address_prefix string
Destination_port_range []string
Direction string
Name string
Priority int
Protocol string
Source_address_prefix string
Source_port_range []string
}
func TestNsgStruct(t *testing.T) {
t.Parallel()
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../terraform/iac1",
NoColor: false,
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
// declare a list of NsgEntry's defined above, into a generic type of map of list of structs
type NsgOutputs map[string][]NsgEntry
// the list type becomes a variable of a "map of list of structs"
var actualTFNsgOutputStruct NsgOutputs
// this validates the shape of each struct (or object in terraform) on its own and assigns to a pointer
terraform.OutputStruct(t, terraformOptions, "nsg", &actualTFNsgOutputStruct)
fmt.Println("ASSIGNMENT INSPECTION:")
// determine the output of the variable being referenced
fmt.Println(reflect.TypeOf(actualTFNsgOutputStruct))
// accessing a variable to demonstrate the shape
fmt.Println(actualTFNsgOutputStruct["AzureWidgets"][0].Access)
} This is really good stuff |
Beta Was this translation helpful? Give feedback.
-
The test highlighted some inadequacies the output my locals blocks were producing - which was exactly what I was originally after. Super pleased - hope this helps someone trying to solve the same problem I had. nsg = {
"AzureBastion" = [
{
"access" = "Allow"
"destination_address_prefix" = "*"
"destination_port_range" = [
"80",
"80",
]
"direction" = "Outbound"
"name" = "AzureBastionOut"
"priority" = 100
"protocol" = "tcp"
"source_address_prefix" = "192.168.1.192/26"
"source_port_range" = [
"*",
]
},
{
"access" = "Allow"
"destination_address_prefix" = "192.168.1.192/26"
"destination_port_range" = [
"80",
"80",
]
"direction" = "Inbound"
"name" = "AzureBastionIn"
"priority" = 110
"protocol" = "tcp"
"source_address_prefix" = "*"
"source_port_range" = [
"*",
]
},
]
"AzureStorage" = [
{
"access" = "Allow"
"destination_address_prefix" = "192.168.1.128/26"
"destination_port_range" = [
"80",
"80",
]
"direction" = "Inbound"
"name" = "AzureStorageIn"
"priority" = 100
"protocol" = "tcp"
"source_address_prefix" = "81.174.249.116/32"
"source_port_range" = [
"*",
]
},
]
"AzureWidgets" = [
{
"access" = "Allow"
"destination_address_prefix" = "192.168.1.128/26"
"destination_port_range" = [
"80",
]
"direction" = "Inbound"
"name" = "AzureWidgetIn"
"priority" = 100
"protocol" = "tcp"
"source_address_prefix" = "81.174.249.116/32"
"source_port_range" = [
"*",
]
},
]
} |
Beta Was this translation helpful? Give feedback.
Hello @niksheridan! It seems like your approach to use a struct to represent the expected shape of Terraform output is reasonable. I believe the issue you are encountering is likely due to how Terraform output is returned as type
map[string]string
(via OutputMap).In your test function, you're using the
IsType
function to assert that the value retrieved from the map is of typeNsgEntry
. This will most likely always fail since the values in the actualTfOutput are of typemap[string]string
instead ofNsgEntry
. If you modify the logic so that it performs a type assertion to convert the values of actualTfOutput toNsgEntry
structs, then you should be able to perform a proper comparison.