forked from hashicorp/packer-plugin-vsphere
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request hashicorp#11 from hashicorp/helper_func
Add hcl2 helper funcs
- Loading branch information
Showing
5 changed files
with
628 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.