Skip to content

Commit

Permalink
Merge pull request hashicorp#11 from hashicorp/helper_func
Browse files Browse the repository at this point in the history
Add hcl2 helper funcs
  • Loading branch information
SwampDragons committed Jan 13, 2021
2 parents fd30ebb + f29f995 commit 33fa1c1
Show file tree
Hide file tree
Showing 5 changed files with 628 additions and 0 deletions.
4 changes: 4 additions & 0 deletions hcl2helper/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*
Package hcl2helper provides helper functions for parsing or getting hcl2 types to and from a Packer plugin config.
*/
package hcl2helper
40 changes: 40 additions & 0 deletions hcl2helper/mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//go:generate mapstructure-to-hcl2 -type MockConfig,NestedMockConfig,MockTag

package hcl2helper

import (
"time"

"github.com/hashicorp/packer-plugin-sdk/template/config"
)

type NestedMockConfig struct {
String string `mapstructure:"string"`
Int int `mapstructure:"int"`
Int64 int64 `mapstructure:"int64"`
Bool bool `mapstructure:"bool"`
Trilean config.Trilean `mapstructure:"trilean"`
Duration time.Duration `mapstructure:"duration"`
MapStringString map[string]string `mapstructure:"map_string_string"`
SliceString []string `mapstructure:"slice_string"`
SliceSliceString [][]string `mapstructure:"slice_slice_string"`
NamedMapStringString NamedMapStringString `mapstructure:"named_map_string_string"`
NamedString NamedString `mapstructure:"named_string"`
Tags []MockTag `mapstructure:"tag"`
Datasource string `mapstructure:"data_source"`
}

type MockTag struct {
Key string `mapstructure:"key"`
Value string `mapstructure:"value"`
}

type MockConfig struct {
NotSquashed string `mapstructure:"not_squashed"`
NestedMockConfig `mapstructure:",squash"`
Nested NestedMockConfig `mapstructure:"nested"`
NestedSlice []NestedMockConfig `mapstructure:"nested_slice"`
}

type NamedMapStringString map[string]string
type NamedString string
133 changes: 133 additions & 0 deletions hcl2helper/mock.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

131 changes: 131 additions & 0 deletions hcl2helper/values.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package hcl2helper

import (
"fmt"
"time"

"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/mitchellh/mapstructure"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
)

// UnknownVariableValue is a sentinel value that can be used
// to denote that the value of a variable is unknown at this time.
// RawConfig uses this information to build up data about
// unknown keys.
const UnknownVariableValue = "74D93920-ED26-11E3-AC10-0800200C9A66"

// HCL2ValueFromConfigValue takes a value turns it into
// a cty.Value so it can be used within, for example, an HCL2 EvalContext.
func HCL2ValueFromConfigValue(v interface{}) cty.Value {
if v == nil {
return cty.NullVal(cty.DynamicPseudoType)
}
if v == UnknownVariableValue {
return cty.DynamicVal
}

switch tv := v.(type) {
case bool:
return cty.BoolVal(tv)
case string:
return cty.StringVal(tv)
case int:
return cty.NumberIntVal(int64(tv))
case float64:
return cty.NumberFloatVal(tv)
case []interface{}:
vals := make([]cty.Value, len(tv))
for i, ev := range tv {
vals[i] = HCL2ValueFromConfigValue(ev)
}
return cty.TupleVal(vals)
case []string:
vals := make([]cty.Value, len(tv))
for i, ev := range tv {
vals[i] = cty.StringVal(ev)
}
return cty.ListVal(vals)
case map[string]interface{}:
vals := map[string]cty.Value{}
for k, ev := range tv {
vals[k] = HCL2ValueFromConfigValue(ev)
}
return cty.ObjectVal(vals)
default:
// HCL/HIL should never generate anything that isn't caught by
// the above, so if we get here something has gone very wrong.
panic(fmt.Errorf("can't convert %#v to cty.Value", v))
}
}

// HCL2ValueFromConfig takes a struct with it's map of hcldec.Spec, and turns it into
// a cty.Value so it can be used as, for example, a Datasource value.
func HCL2ValueFromConfig(conf interface{}, configSpec map[string]hcldec.Spec) cty.Value {
c := map[string]interface{}{}
if err := mapstructure.Decode(conf, &c); err != nil {
panic(fmt.Errorf("can't convert %#v to cty.Value", conf))
}

// Use the HCL2Spec to know the expected cty.Type for an attribute
resp := map[string]cty.Value{}
for k, v := range c {
spec := configSpec[k]

switch st := spec.(type) {
case *hcldec.BlockListSpec:
// This should be a slice of objects, so we need to take a special care
if hcldec.ImpliedType(st.Nested).IsObjectType() {
res := []cty.Value{}
c := []interface{}{}
if err := mapstructure.Decode(v, &c); err != nil {
panic(fmt.Errorf("can't convert %#v to cty.Value", conf))
}
types := hcldec.ChildBlockTypes(spec)
for _, e := range c {
res = append(res, HCL2ValueFromConfig(e, types[k].(hcldec.ObjectSpec)))
}
if len(res) != 0 {
resp[k] = cty.ListVal(res)
continue
}
// At this point this is an empty list so we want it to go to gocty.ToCtyValue(v, impT)
// and make it a NullVal
}
}

impT := hcldec.ImpliedType(spec)
if value, err := gocty.ToCtyValue(v, impT); err == nil {
resp[k] = value
continue
}

// Uncommon types not caught until now
switch tv := v.(type) {
case config.Trilean:
resp[k] = cty.BoolVal(tv.True())
continue
case time.Duration:
if tv.Microseconds() == int64(0) {
resp[k] = cty.NumberIntVal(int64(0))
continue
}
resp[k] = cty.NumberIntVal(v.(time.Duration).Milliseconds())
continue
}

// This is a nested object and we should recursively go through the same process
if impT.IsObjectType() {
types := hcldec.ChildBlockTypes(spec)
resp[k] = HCL2ValueFromConfig(v, types[k].(hcldec.ObjectSpec))
continue
}

panic("not supported type - contact the Packer team with further information")
}

// This is decoding structs so it will always be an cty.ObjectVal at the end
return cty.ObjectVal(resp)
}
Loading

0 comments on commit 33fa1c1

Please sign in to comment.