hashicorp / hcl

HCL is the HashiCorp configuration language.
Mozilla Public License 2.0
5.16k stars 583 forks source link

Can dynamic names be used in a Block spec-definition to identify the block? #367

Closed k86021 closed 4 years ago

k86021 commented 4 years ago

HCL Template

# source.hcl
source json "sirius" {
    key = "test"
   attr adresse  {
      type = "varchar"
      expr = "$.sti.feltnavn"
    }
 }

# target.hcl
target hub "X" {
  key=[source.json.sirius.key, source.json.sirius.attr.adresse.expr, source.json.sirius.adresse.expr, "verdi"]
}

spec:

    // spec for source-files
    spec := hcldec.ObjectSpec{
        "source": &hcldec.BlockMapSpec{
            TypeName:   "source",
            LabelNames: []string{"type", "name"},
            Nested: hcldec.ObjectSpec{
                "key" : &hcldec.AttrSpec{
                    Name:     "key",
                    Type:     cty.String,
                    Required: false,
                },
                "attr": &hcldec.BlockMapSpec{
                    TypeName:   "attr",
                    LabelNames: []string{"name"},
                    Nested: &hcldec.ObjectSpec{
                        "type": &hcldec.AttrSpec{
                            Name:    "type",
                            Type:     cty.String,
                            Required: false,
                        },
                        "expr": &hcldec.AttrSpec{
                            Name:     "expr",
                            Type:     cty.String,
                            Required: false,
                        },
                    },
                },
            },
        },
    }

    // spec  for target-files
    tspec := hcldec.ObjectSpec{
        "target": &hcldec.BlockMapSpec{
            TypeName:   "target",
            LabelNames: []string{"type", "name"},
            Nested: hcldec.ObjectSpec{
                "key": &hcldec.AttrSpec{
                        Name:     "key",
                        Type:     cty.List(cty.String),
                        Required: true,
                    },
                },
            },
        }

In this example the "attr"-name is used for a BlockMapSpec, but I'm looking for a way to simplify the path for the target attributes by having the "attr"-substring from the target.hcl file removed, i.e. as can be seen from the target.hcl above:

I would like to be able to define a block based on this pattern compared to the source.hcl above, i.e. without the "attr" before "adresse", "navn" etc.:

source json "sirius" {
    key = "test"
    adresse  {
      type = "varchar"
      expr = "$.path.adresse"
    }
   navn {
      type = "varchar"
      expr = "$.path.navn"
    }
...
 }

Is this possible?

azr commented 4 years ago

Hey @k86021 I'm not sure if I understood your need but I will try to give pointers to solutions that I think should help you:

HCL2 can read files sort of partially and one thing you can do is use the remain tag to sort of partially read a config, here's an example code in Packer, link :

func (p *Parser) decodeProvisioner(block *hcl.Block) (*ProvisionerBlock, hcl.Diagnostics) {
    var b struct {
        Name        string   `hcl:"name,optional"`
        PauseBefore string   `hcl:"pause_before,optional"`
        MaxRetries  int      `hcl:"max_retries,optional"`
        Timeout     string   `hcl:"timeout,optional"`
        Rest        hcl.Body `hcl:",remain"`
    }
    diags := gohcl.DecodeBody(block.Body, nil, &b)
    if diags.HasErrors() {
        return nil, diags
    }

    provisioner := &ProvisionerBlock{
        PType:      block.Labels[0],

Here block.Labels[0] is the type of the Packer provisioner ( so it could be shell, file, ect. ) , Name, PauseBefore, MaxRetries & Timeout are common fields to all provisioners and the Rest member of that local struct is passed to the provisioner starter, and so that starter sort of knows what to do with it and may be you could reuse the Rest: So if we could just return a cty.Value from that Rest; then that value could be put in an EvalContext.

k86021 commented 4 years ago

Thanks again @azr . Have testet some different solutions now and ended up with a struct for the target and using the spec for the source. This works for us so I think we will leave it like that. The target structs looks like this:

type TableCols struct {
    Name     string    `hcl:"name,label"`
    Source   cty.Value `hcl:"source,optional"`
    Expr     string    `hcl:"expr,optional"`
    Type     string    `hcl:"type,optional"`
    Length   string    `hcl:"length,optional"`
    Location string    `hcl:"location,optional"`
}
type Body struct {
    Type string      `hcl:",label"`
    Name string      `hcl:",label"`
    Cols []TableCols `hcl:"cols,block"`
}

type Root struct {
    Bodies []Body   `hcl:"target,block"`
    Remain hcl.Body `hcl:"default,remain"`
}

Cheers /b

azr commented 4 years ago

Awesome ! :D