How to fetch attribute type from terraform provider schema

Hi Team, am looking for some help around fetching the attribute type for each attribute - which got generated from running " terraform providers schema -JSON". I’d need to store them in the cty.Type value. I was able to fetch simple type constraints like “string, bool, number” but need some help on complex types.

I’ve even raised it on StackOverflow but unfortunately didn’t get any response till now.

Can someone please help me with this ?

Thanks in advance.

Stackoverflow : fetch attribute type from terraform provider schema

Hi @saad-uddin,

It would be helpful if you could share what you wrote so far that is able to fetch simple type constraints. Then hopefully we can extend that to support more complex type constraints.

Hi @apparentlymart .

This is what I have so far.

package main

import (
	// "bytes"
	"os"
    "log"
    "fmt"
	"github.com/buger/jsonparser"
    "github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/hclwrite"
	"github.com/zclconf/go-cty/cty"
    // "github.com/hashicorp/hcl/v2/hclsyntax"
    // "reflect"
    "sort"
    "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"encoding/json"
    tfjson "github.com/hashicorp/terraform-json"
)


func main(){

    vars := hclwrite.NewEmptyFile()
    vars_root_body := vars.Body()
    vars_file, vars_create_err := os.Create("variables.tf")
    if vars_create_err != nil{
        log.Fatal(vars_create_err)
    }
    
    vars_block := vars_root_body.AppendNewBlock("variable",[]string{"default_variable"})
    vars_block_body := vars_block.Body()

    vars_block_body.SetAttributeRaw(
        "type",
        getVariableType(cty.Tuple([]cty.Type{cty.Bool,cty.String})),
    )
    fmt.Printf("%s", vars.Bytes())
}


func getVariableType(cty_type cty.Type) hclwrite.Tokens {

    switch cty_type {

    case cty.String:
        return hclwrite.TokensForIdentifier("string")
    case cty.Bool:
        return hclwrite.TokensForIdentifier("bool")
    case cty.Number:
        return hclwrite.TokensForIdentifier("number")
    case cty.DynamicPseudoType:
        return hclwrite.TokensForIdentifier("any")
    case cty.NilType:
        return hclwrite.TokensForIdentifier("null")
    }

    if cty_type.IsCollectionType() {
        etyTokens := getVariableType(cty_type.ElementType())

        switch {
        case cty_type.IsListType():
            return hclwrite.TokensForFunctionCall("list", etyTokens)
        case cty_type.IsSetType():
            return hclwrite.TokensForFunctionCall("set", etyTokens)
        case cty_type.IsMapType():
            return hclwrite.TokensForFunctionCall("map", etyTokens)
        default:
            // Should never happen because the above is exhaustive
            panic("unsupported collection type")
        }
    }

    if cty_type.IsObjectType() {
        atys := cty_type.AttributeTypes()
        names := make([]string, 0, len(atys))
        for name := range atys {
            names = append(names, name)
        }
        sort.Strings(names)

        items := make([]hclwrite.ObjectAttrTokens, len(names))
        for i, name := range names {
            items[i] = hclwrite.ObjectAttrTokens{
                Name:  hclwrite.TokensForIdentifier(name),
                Value: getVariableType(atys[name]),
            }
        }

        return hclwrite.TokensForObject(items)
    }

    if cty_type.IsTupleType() {
        fmt.Println("Inside Tuple")
        etys := cty_type.TupleElementTypes()
        items := make([]hclwrite.Tokens, len(etys))
        for i, ety := range etys {
            items[i] = getVariableType(ety)
        }
        return hclwrite.TokensForTuple(items)
    }

    panic(fmt.Errorf("unsupported type %#v", cty_type))

}

So for now am manually passing the type constraint to this “getVariableType” func , and getting the type. What I am looking for is to read the type constraint value for nested types such as " "type": [ "map", "string" ], or "type": [ "list", "string" ], or more complex like this : "type": [ "list", [ "object", { "metadata": [ "list", [ "object", { "annotations": [ "map", "string" ], "generation": "number", "labels": [ "map", "string" ], "name": "string", "namespace": "string", "resource_version": "string", "self_link": "string", "uid": "string" } ] ], "spec": [ "list", [ "object", { "container_concurrency": "number", "containers": [ "list", [ "object", { "args": [ "list", "string" ], "command": [ "list", "string" ], "env": [ "set", [ "object", { "name": "string", "value": "string", "value_from": [ "list", [ "object", { "secret_key_ref": [ "list", [ "object", { "key": "string", "name": "string" } ] ] } ] ] } ] ], "env_from": [ "list", [ "object", { "config_map_ref": [ "list", [ "object", { "local_object_reference": [ "list", [ "object", { "name": "string" } ] ], "optional": "bool" } ] ], "prefix": "string", "secret_ref": [ "list", [ "object", { "local_object_reference": [ "list", [ "object", { "name": "string" } ] ], "optional": "bool" } ] ] } ] ], "image": "string", "ports": [ "list", [ "object", { "container_port": "number", "name": "string", "protocol": "string" } ] ], "resources": [ "list", [ "object", { "limits": [ "map", "string" ], "requests": [ "map", "string" ] } ] ], "volume_mounts": [ "list", [ "object", { "mount_path": "string", "name": "string" } ] ], "working_dir": "string" } ] ], "service_account_name": "string", "serving_state": "string", "timeout_seconds": "number", "volumes": [ "list", [ "object", { "name": "string", "secret": [ "list", [ "object", { "default_mode": "number", "items": [ "list", [ "object", { "key": "string", "mode": "number", "path": "string" } ] ], "secret_name": "string" } ] ] } ] ] } ] ] } ], "test" ]

which becomes more complex for nested type constraints.

All this is coming from the json file which I got from running “terraform providers schema -json”

The above func was also a courtesy from StackOverflow help (go - Get the type of value using cty in hclwrite - Stack Overflow)

I would really really appreciate if you help me one example of how we can get the type constraint for a nested type - and I can try from there on for more complex types.

Can we terraform-json go-pkg to fetch the type ? and then store it in cty.type and then parse it via hcl pkg ?

Hi @saad-uddin,

The terraform-json package has type ProviderSchemas which represents the structure of terraform provider schemas -json. If you decode into that you should find a cty.Type value in each of the attribute schemas.

Hi @apparentlymart Yea i’ve taken a look at that terraform-json pkg, but unfortunately unable to figure out how to decode the types (am a newbie at golang). I am really sorry to ask this, but can you please help me with small example code of how to fetch the type constraint using the above pkg ? I shall your example as a stepping stone and will explore further. I really need some help here, and would highly appreciate your support on this. Thanks in advance.

Hi @saad-uddin,

Unfortunately I’m not as familiar with terraform-json as I am with hcl and so I can’t so easily write a full working example.

The usual way to parse JSON in Go is to declare a variable of the target type and then pass it to the encoding/json package’s Unmarshal function. That function uses reflection to find out which type of value you’ve passed and decide how to map the given JSON to that type.

So assuming you already have the JSON source code from terraform providers schema -json in a variable called src which is of type []byte, I expect it will work to decode into a variable of type ProviderSchemas like this:

    var schemas tfjson.ProviderSchemas
    err := json.Unmarshal(src, &schemas)
    if err != nil {
        # (handle the error)
    }

If json.Unmarshal does not return an error then you can work with the data loaded into schemas.Schemas, which is a map of ProviderSchema objects.