Files
Packer-Cn/helper/config/decode.go
T

285 lines
7.9 KiB
Go
Raw Normal View History

2015-05-27 10:44:10 -07:00
package config
import (
2019-12-17 11:25:56 +01:00
"encoding/json"
2015-05-27 11:34:47 -07:00
"fmt"
2020-01-13 15:52:05 -08:00
"log"
2015-05-27 10:44:10 -07:00
"reflect"
2015-05-27 11:34:47 -07:00
"sort"
"strings"
2015-05-27 10:44:10 -07:00
2015-05-27 11:34:47 -07:00
"github.com/hashicorp/go-multierror"
2019-12-17 11:25:56 +01:00
"github.com/hashicorp/hcl/v2/hcldec"
2017-04-04 13:39:01 -07:00
"github.com/hashicorp/packer/template/interpolate"
2015-05-27 10:44:10 -07:00
"github.com/mitchellh/mapstructure"
2019-12-17 11:25:56 +01:00
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
ctyjson "github.com/zclconf/go-cty/cty/json"
2015-05-27 10:44:10 -07:00
)
// DecodeOpts are the options for decoding configuration.
type DecodeOpts struct {
2015-05-27 12:55:41 -07:00
// Metadata, if non-nil, will be set to the metadata post-decode
Metadata *mapstructure.Metadata
2015-05-27 10:44:10 -07:00
// Interpolate, if true, will automatically interpolate the
// configuration with the given InterpolateContext. User variables
// will be automatically detected and added in-place to the given
// context.
Interpolate bool
InterpolateContext *interpolate.Context
InterpolateFilter *interpolate.RenderFilter
DecodeHooks []mapstructure.DecodeHookFunc
}
var DefaultDecodeHookFuncs = []mapstructure.DecodeHookFunc{
uint8ToStringHook,
stringToTrilean,
mapstructure.StringToSliceHookFunc(","),
mapstructure.StringToTimeDurationHookFunc(),
2015-05-27 10:44:10 -07:00
}
// Decode decodes the configuration into the target and optionally
// automatically interpolates all the configuration as it goes.
func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error {
2019-12-17 11:25:56 +01:00
for i, raw := range raws {
// check for cty values and transform them to json then to a
// map[string]interface{} so that mapstructure can do its thing.
cval, ok := raw.(cty.Value)
if !ok {
continue
}
type flatConfigurer interface {
FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec }
}
ctarget := target.(flatConfigurer)
flatCfg := ctarget.FlatMapstructure()
err := gocty.FromCtyValue(cval, flatCfg)
if err != nil {
switch err := err.(type) {
case cty.PathError:
return fmt.Errorf("%v: %v", err, err.Path)
}
return err
}
b, err := ctyjson.SimpleJSONValue{Value: cval}.MarshalJSON()
if err != nil {
return err
}
var raw map[string]interface{}
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
raws[i] = raw
}
2015-05-27 10:44:10 -07:00
if config == nil {
config = &DecodeOpts{Interpolate: true}
}
// Detect user variables from the raws and merge them into our context
ctxData, raws := DetectContextData(raws...)
2015-05-27 10:44:10 -07:00
// Interpolate first
if config.Interpolate {
ctx, err := DetectContext(raws...)
if err != nil {
return err
}
if config.InterpolateContext == nil {
config.InterpolateContext = ctx
} else {
config.InterpolateContext.BuildName = ctx.BuildName
config.InterpolateContext.BuildType = ctx.BuildType
2015-06-13 22:56:36 -07:00
config.InterpolateContext.TemplatePath = ctx.TemplatePath
2015-05-27 10:44:10 -07:00
config.InterpolateContext.UserVariables = ctx.UserVariables
if config.InterpolateContext.Data == nil {
config.InterpolateContext.Data = ctxData
}
2015-05-27 10:44:10 -07:00
}
ctx = config.InterpolateContext
// Render everything
for i, raw := range raws {
m, err := interpolate.RenderMap(raw, ctx, config.InterpolateFilter)
if err != nil {
return err
}
raws[i] = m
}
}
decodeHookFuncs := DefaultDecodeHookFuncs
if len(config.DecodeHooks) != 0 {
decodeHookFuncs = config.DecodeHooks
}
2015-05-27 10:44:10 -07:00
// Build our decoder
var md mapstructure.Metadata
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: target,
Metadata: &md,
WeaklyTypedInput: true,
DecodeHook: mapstructure.ComposeDecodeHookFunc(decodeHookFuncs...),
2015-05-27 10:44:10 -07:00
})
if err != nil {
return err
}
for _, raw := range raws {
if err := decoder.Decode(raw); err != nil {
2015-05-27 11:34:47 -07:00
return err
}
}
2015-05-27 12:55:41 -07:00
// Set the metadata if it is set
if config.Metadata != nil {
*config.Metadata = md
}
2015-05-27 11:34:47 -07:00
// If we have unused keys, it is an error
if len(md.Unused) > 0 {
var err error
sort.Strings(md.Unused)
for _, unused := range md.Unused {
if unused != "type" && !strings.HasPrefix(unused, "packer_") {
2020-05-28 16:14:17 -07:00
// Check for whether the key is handled in a packer fix
// call.
fixable := false
for _, deprecatedOption := range DeprecatedOptions {
if unused == deprecatedOption {
fixable = true
break
}
}
unusedErr := fmt.Errorf("unknown configuration key: '%q'",
unused)
if fixable {
unusedErr = fmt.Errorf("Deprecated configuration key: '%s'."+
" Please call `packer fix` against your template to "+
"update your template to be compatible with the current "+
2020-05-28 16:14:17 -07:00
"version of Packer. Visit "+
"https://www.packer.io/docs/commands/fix/ for more detail.",
unused)
}
err = multierror.Append(err, unusedErr)
2015-05-27 11:34:47 -07:00
}
}
if err != nil {
2015-05-27 10:44:10 -07:00
return err
}
}
return nil
}
func DetectContextData(raws ...interface{}) (map[interface{}]interface{}, []interface{}) {
// In provisioners, the last value pulled from raws is the placeholder data
// for build-specific variables. Pull these out to add to interpolation
// context.
2020-01-13 15:52:05 -08:00
if len(raws) == 0 {
return nil, raws
}
// Internally, our tests may cause this to be read as a map[string]string
placeholderData := raws[len(raws)-1]
if pd, ok := placeholderData.(map[string]string); ok {
if uuid, ok := pd["PackerRunUUID"]; ok {
if strings.Contains(uuid, "Build_PackerRunUUID.") {
cast := make(map[interface{}]interface{})
for k, v := range pd {
cast[k] = v
}
raws = raws[:len(raws)-1]
return cast, raws
}
}
}
// but with normal interface conversion across the rpc, it'll look like a
// map[interface]interface, not a map[string]string
if pd, ok := placeholderData.(map[interface{}]interface{}); ok {
if uuid, ok := pd["PackerRunUUID"]; ok {
if strings.Contains(uuid.(string), "Build_PackerRunUUID.") {
raws = raws[:len(raws)-1]
return pd, raws
}
}
}
return nil, raws
}
2015-05-27 10:44:10 -07:00
// DetectContext builds a base interpolate.Context, automatically
// detecting things like user variables from the raw configuration params.
func DetectContext(raws ...interface{}) (*interpolate.Context, error) {
var s struct {
BuildName string `mapstructure:"packer_build_name"`
BuildType string `mapstructure:"packer_builder_type"`
TemplatePath string `mapstructure:"packer_template_path"`
Vars map[string]string `mapstructure:"packer_user_variables"`
SensitiveVars []string `mapstructure:"packer_sensitive_variables"`
2015-05-27 10:44:10 -07:00
}
for _, r := range raws {
if err := mapstructure.Decode(r, &s); err != nil {
2020-01-13 15:52:05 -08:00
log.Printf("Error detecting context: %s", err)
2015-05-27 10:44:10 -07:00
return nil, err
}
}
return &interpolate.Context{
BuildName: s.BuildName,
BuildType: s.BuildType,
TemplatePath: s.TemplatePath,
UserVariables: s.Vars,
SensitiveVariables: s.SensitiveVars,
2015-05-27 10:44:10 -07:00
}, nil
}
func uint8ToStringHook(f reflect.Kind, t reflect.Kind, v interface{}) (interface{}, error) {
// We need to convert []uint8 to string. We have to do this
// because internally Packer uses MsgPack for RPC and the MsgPack
// codec turns strings into []uint8
if f == reflect.Slice && t == reflect.String {
dataVal := reflect.ValueOf(v)
dataType := dataVal.Type()
elemKind := dataType.Elem().Kind()
if elemKind == reflect.Uint8 {
v = string(dataVal.Interface().([]uint8))
}
}
return v, nil
}
func stringToTrilean(f reflect.Type, t reflect.Type, v interface{}) (interface{}, error) {
// We have a custom data type, config, which we read from a string and
// then cast to a *bool. Why? So that we can appropriately read "unset"
// *bool values in order to intelligently default, even when the values are
// being set by a template variable.
testTril, _ := TrileanFromString("")
if t == reflect.TypeOf(testTril) {
// From value is string
if f == reflect.TypeOf("") {
tril, err := TrileanFromString(v.(string))
if err != nil {
return v, fmt.Errorf("Error parsing bool from given var: %s", err)
}
return tril, nil
} else {
// From value is boolean
if f == reflect.TypeOf(true) {
tril := TrileanFromBool(v.(bool))
return tril, nil
}
}
}
return v, nil
}