HiveBrain v1.2.0
Get Started
← Back to all entries
patterngoMinor

Analyze terraform template files to return infrastructure cost

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
templateanalyzereturninfrastructurefilescostterraform

Problem

I am trying to write a program I am calling "terraform cashier." Terraform is a tool released by hashicorp to allow you to codify your infrastructure. I thought it would be useful to know how much your infrastructure would cost you before you deployed it, and have implemented a prototype of that for AWS. I also had to write a pricing data ingestion tool and API to power this particular tool, but those seem to be working fine, so I'd like to keep the review to the cli tool itself.

The repo can be found here.

I do a lot of type casting in the processTerraformFile function. How can I avoid that? Am I processing the terraform files correctly? A particular ugly segment of the function is below. The main goal of the function is to import the data in the terraform templates so that it can be processed inside of cashier. I'm just identifying the resources and counting them.

arrayOfResources, success := decodedOutput["resource"].([]map[string]interface{})

if success == true {
    for _, resource := range arrayOfResources {
        for key := range resource {
            switch key {
            case "aws_instance":
                resourceKeys, resourceKeysSuccess := resource[key].([]map[string]interface{})
                if resourceKeysSuccess == true {
                    for resourceKey := range resourceKeys[0] {
                        instanceType, instanceTypeSuccess := resourceKeys[0][resourceKey].([]map[string]interface{})[0]["instance_type"].(string)
                        if instanceTypeSuccess == true {
                            masterResourceMap = countResource(masterResourceMap, key, instanceType)
                        }
                    }
                }
            default:
                fmt.Println("resource type not recognized: ", key)
            }
        }
    }
}


I wrote some basic tests as well, but decided to not test the main() function. Is this standard? It started to feel strange when I was making API calls in my test.

Solution

Usually, the code is a bit worse, indentation-wise:

if res, ok := decodedOutput["resource"].([]map[string]interface{}); ok {
    for _, resource := range res {
        for key := range resource {
            switch key {
            case "aws_instance":
                if rk, ok := resource[key].([]map[string]interface{}); ok {
                    for resourceKey := range rk[0] {
                        if itype, ok := resourceKeys[0][resourceKey].([]map[string]interface{}); ok {
                            if d, ok := itype[0]["instance_type"].(string); ok {
                                masterResourceMap = countResource(masterResourceMap, key, d)
                            }
                        }
                    }
                }

            default:
                fmt.Println("resource type not recognized: ", key)
            }
        }
    }
}


Now, from a Terraform point-of-view, you already have a semi-assurance that the type you're trying to cast has been cleaned up, this due to how you declare the values of the terraform file. You usually define it with the schema package, so while it's running, you know Terraform already checked the types for you.

The overall I've seen is that providers, for example, do not tests against a true type casting from interfaces, again, due to Terraform pre-checking the information to match the schema definition, so if it is a string but someone passed an int, then Terraform will exit and fail way earlier than reaching your type casting.

Code Snippets

if res, ok := decodedOutput["resource"].([]map[string]interface{}); ok {
    for _, resource := range res {
        for key := range resource {
            switch key {
            case "aws_instance":
                if rk, ok := resource[key].([]map[string]interface{}); ok {
                    for resourceKey := range rk[0] {
                        if itype, ok := resourceKeys[0][resourceKey].([]map[string]interface{}); ok {
                            if d, ok := itype[0]["instance_type"].(string); ok {
                                masterResourceMap = countResource(masterResourceMap, key, d)
                            }
                        }
                    }
                }

            default:
                fmt.Println("resource type not recognized: ", key)
            }
        }
    }
}

Context

StackExchange Code Review Q#160044, answer score: 2

Revisions (0)

No revisions yet.