2014-06-12 18:40:53 -05:00
|
|
|
package config
|
|
|
|
|
2014-06-12 19:24:55 -05:00
|
|
|
import (
|
2014-06-20 12:33:26 -05:00
|
|
|
"bytes"
|
|
|
|
"encoding/gob"
|
|
|
|
|
2014-06-12 19:24:55 -05:00
|
|
|
"github.com/mitchellh/copystructure"
|
|
|
|
"github.com/mitchellh/reflectwalk"
|
|
|
|
)
|
|
|
|
|
2014-06-12 18:40:53 -05:00
|
|
|
// 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"
|
|
|
|
|
|
|
|
// RawConfig is a structure that holds a piece of configuration
|
|
|
|
// where te overall structure is unknown since it will be used
|
|
|
|
// to configure a plugin or some other similar external component.
|
|
|
|
//
|
|
|
|
// RawConfigs can be interpolated with variables that come from
|
|
|
|
// other resources, user variables, etc.
|
|
|
|
//
|
|
|
|
// RawConfig supports a query-like interface to request
|
|
|
|
// information from deep within the structure.
|
|
|
|
type RawConfig struct {
|
|
|
|
Raw map[string]interface{}
|
|
|
|
Variables map[string]InterpolatedVariable
|
2014-06-12 19:24:55 -05:00
|
|
|
|
|
|
|
config map[string]interface{}
|
|
|
|
unknownKeys []string
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewRawConfig creates a new RawConfig structure and populates the
|
|
|
|
// publicly readable struct fields.
|
|
|
|
func NewRawConfig(raw map[string]interface{}) (*RawConfig, error) {
|
2014-06-20 12:33:26 -05:00
|
|
|
result := &RawConfig{Raw: raw}
|
|
|
|
if err := result.init(); err != nil {
|
2014-06-12 19:24:55 -05:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-06-20 12:33:26 -05:00
|
|
|
return result, nil
|
2014-06-12 18:40:53 -05:00
|
|
|
}
|
|
|
|
|
2014-06-12 19:27:53 -05:00
|
|
|
// Config returns the entire configuration with the variables
|
|
|
|
// interpolated from any call to Interpolate.
|
|
|
|
//
|
|
|
|
// If any interpolated variables are unknown (value set to
|
|
|
|
// UnknownVariableValue), the first non-container (map, slice, etc.) element
|
|
|
|
// will be removed from the config. The keys of unknown variables
|
|
|
|
// can be found using the UnknownKeys function.
|
|
|
|
//
|
|
|
|
// By pruning out unknown keys from the configuration, the raw
|
|
|
|
// structure will always successfully decode into its ultimate
|
|
|
|
// structure using something like mapstructure.
|
|
|
|
func (r *RawConfig) Config() map[string]interface{} {
|
|
|
|
return r.config
|
|
|
|
}
|
|
|
|
|
2014-06-12 18:40:53 -05:00
|
|
|
// Interpolate uses the given mapping of variable values and uses
|
|
|
|
// those as the values to replace any variables in this raw
|
|
|
|
// configuration.
|
|
|
|
//
|
|
|
|
// Any prior calls to Interpolate are replaced with this one.
|
|
|
|
//
|
|
|
|
// If a variable key is missing, this will panic.
|
2014-06-12 19:24:55 -05:00
|
|
|
func (r *RawConfig) Interpolate(vs map[string]string) error {
|
|
|
|
config, err := copystructure.Copy(r.Raw)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
w := &variableReplaceWalker{Values: vs}
|
|
|
|
r.config = config.(map[string]interface{})
|
|
|
|
err = reflectwalk.Walk(r.config, w)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
r.unknownKeys = w.UnknownKeys
|
|
|
|
return nil
|
2014-06-12 18:40:53 -05:00
|
|
|
}
|
|
|
|
|
2014-06-20 12:33:26 -05:00
|
|
|
func (r *RawConfig) init() error {
|
|
|
|
walker := new(variableDetectWalker)
|
|
|
|
if err := reflectwalk.Walk(r.Raw, walker); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Variables = walker.Variables
|
|
|
|
r.config = r.Raw
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-06-12 19:24:55 -05:00
|
|
|
// UnknownKeys returns the keys of the configuration that are unknown
|
|
|
|
// because they had interpolated variables that must be computed.
|
|
|
|
func (r *RawConfig) UnknownKeys() []string {
|
|
|
|
return r.unknownKeys
|
2014-06-12 18:40:53 -05:00
|
|
|
}
|
2014-06-20 12:33:26 -05:00
|
|
|
|
|
|
|
// See GobEncode
|
|
|
|
func (r *RawConfig) GobDecode(b []byte) error {
|
|
|
|
err := gob.NewDecoder(bytes.NewReader(b)).Decode(&r.Raw)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return r.init()
|
|
|
|
}
|
|
|
|
|
|
|
|
// GobEncode is a custom Gob encoder to use so that we only include the
|
|
|
|
// raw configuration. Interpolated variables and such are lost and the
|
|
|
|
// tree of interpolated variables is recomputed on decode, since it is
|
|
|
|
// referentially transparent.
|
|
|
|
func (r *RawConfig) GobEncode() ([]byte, error) {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if err := gob.NewEncoder(&buf).Encode(r.Raw); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf.Bytes(), nil
|
|
|
|
}
|