Home > Blockchain >  HCL Decoding: Blocks with multiple labels
HCL Decoding: Blocks with multiple labels

Time:05-12

My goal is to parse a HCL configuration (Terraform Configuration) and then write the collected data about variables, outputs, resources blocks and data blocks into a Markdown file.

Variables and outputs are no problem, however, as soon as I trying to decode resource blocks, which have multiple labels.

Works:

variable "foo" {
  type = "bar"
}

Doesn't Work:

resource "foo" "bar" {
 name = "biz"
}

Error: Extraneous label for resource; Only 1 labels (name) are expected for resource blocks.

Type declaration Code:

import (
    "log"
    "os"
    "strconv"

    "github.com/hashicorp/hcl/v2"
    "github.com/hashicorp/hcl/v2/gohcl"
    "github.com/hashicorp/hcl/v2/hclsyntax"
)

type Variable struct {
    Name        string         `hcl:",label"`
    Description string         `hcl:"description,optional"`
    Sensitive   bool           `hcl:"sensitive,optional"`
    Type        *hcl.Attribute `hcl:"type,optional"`
    Default     *hcl.Attribute `hcl:"default,optional"`
    Options     hcl.Body       `hcl:",remain"`
}

type Output struct {
    Name        string   `hcl:",label"`
    Description string   `hcl:"description,optional"`
    Sensitive   bool     `hcl:"sensitive,optional"`
    Value       string   `hcl:"value,optional"`
    Options     hcl.Body `hcl:",remain"`
}

type Resource struct {
    Name    string   `hcl:"name,label"`
    Options hcl.Body `hcl:",remain"`
}

type Data struct {
    Name    string   `hcl:"name,label"`
    Options hcl.Body `hcl:",remain"`
}

type Config struct {
    Outputs   []*Output   `hcl:"output,block"`
    Variables []*Variable `hcl:"variable,block"`
    Resources []*Resource `hcl:"resource,block"`
    Data      []*Data     `hcl:"data,block"`
}

Decoding Code:

func createDocs(hclPath string) map[string][]map[string]string {
    var variables, outputs []map[string]string

    parsedConfig := make(map[string][]map[string]string)
    hclConfig := make(map[string][]byte)

    c := &Config{}

    // Iterate all Terraform files and safe the contents in the hclConfig map
    for _, file := range filesInDirectory(hclPath, ".tf") {
        fileContent, err := os.ReadFile(hclPath   "/"   file.Name())
        if err != nil {
            log.Fatal(err)
        }
        hclConfig[file.Name()] = fileContent
    }

    // Iterate all file contents
    for k, v := range hclConfig {
        parsedConfig, diags := hclsyntax.ParseConfig(v, k, hcl.Pos{Line: 1, Column: 1})
        if diags.HasErrors() {
            log.Fatal(diags)
        }

        diags = gohcl.DecodeBody(parsedConfig.Body, nil, c)
        if diags.HasErrors() {
            log.Fatal(diags)
        }
    }

    for _, v := range c.Variables {
        var variableType string
        var variableDefault string

        if v.Type != nil {
            variableType = (v.Type.Expr).Variables()[0].RootName()
        }

        if v.Default != nil {
            variableDefault = (v.Default.Expr).Variables()[0].RootName()
        }

        variables = append(variables, map[string]string{"name": v.Name, "description": v.Description,
            "sensitive": strconv.FormatBool(v.Sensitive), "type": variableType, "default": variableDefault})
    }

    for _, v := range c.Outputs {
        outputs = append(outputs, map[string]string{"name": v.Name, "description": v.Description,
            "sensitive": strconv.FormatBool(v.Sensitive), "value": v.Value})
    }

    parsedConfig["variables"], parsedConfig["outputs"] = variables, outputs

    return parsedConfig
}

Question: How can I parse multiple labels from resource blocks?

CodePudding user response:

The error you shared is due to the definition of type Resource. resource blocks (and data blocks) in Terraform expect two labels, indicating the resource type and name. To match that in the schema you're implying with these struct types, you'll need to define to fields that are tagged as label:

type Resource struct {
    Type    string   `hcl:"type,label"`
    Name    string   `hcl:"name,label"`
    Options hcl.Body `hcl:",remain"`
}

type Data struct {
    Type    string   `hcl:"type,label"`
    Name    string   `hcl:"name,label"`
    Options hcl.Body `hcl:",remain"`
}

Although this should work for the limited input you showed here, I want to caution that you are using the higher-level gohcl API which can decode only a subset of HCL that maps well onto Go's struct types. Terraform itself uses the lower-level APIs of hcl.Body and hcl.Expression directly, which allows the Terraform language to include some HCL features that the gohcl API cannot directly represent.

Depending on what your goal is, you may find it better to use the official library terraform-config-inspect, which can parse, decode, and describe a subset of the Terraform language at a higher level of abstraction than the HCL API itself. It also supports modules written for Terraform versions going all the way back to Terraform v0.11, and is the implementation that backs the analysis of modules done by Terraform Registry.

  • Related