Update interface of Interpolate

Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
Daniel Nephin 2017-10-03 16:22:02 -04:00
parent edcea7c7a6
commit 0aa7ca943c
2 changed files with 62 additions and 25 deletions

View File

@ -1,75 +1,88 @@
package interpolation
import (
"os"
"github.com/docker/cli/cli/compose/template"
"github.com/pkg/errors"
)
// Options supported by Interpolate
type Options struct {
// SectionName of the configuration section
SectionName string
// LookupValue from a key
LookupValue LookupValue
}
// LookupValue is a function which maps from variable names to values.
// Returns the value as a string and a bool indicating whether
// the value is present, to distinguish between an empty string
// and the absence of a value.
type LookupValue func(key string) (string, bool)
// Interpolate replaces variables in a string with the values from a mapping
func Interpolate(config map[string]interface{}, section string, mapping template.Mapping) (map[string]interface{}, error) {
func Interpolate(config map[string]interface{}, opts Options) (map[string]interface{}, error) {
out := map[string]interface{}{}
for name, item := range config {
if opts.LookupValue == nil {
opts.LookupValue = os.LookupEnv
}
for key, item := range config {
if item == nil {
out[name] = nil
out[key] = nil
continue
}
mapItem, ok := item.(map[string]interface{})
if !ok {
return nil, errors.Errorf("Invalid type for %s : %T instead of %T", name, item, out)
return nil, errors.Errorf("Invalid type for %s : %T instead of %T", key, item, out)
}
interpolatedItem, err := interpolateSectionItem(name, mapItem, section, mapping)
interpolatedItem, err := interpolateSectionItem(key, mapItem, opts)
if err != nil {
return nil, err
}
out[name] = interpolatedItem
out[key] = interpolatedItem
}
return out, nil
}
func interpolateSectionItem(
name string,
sectionkey string,
item map[string]interface{},
section string,
mapping template.Mapping,
opts Options,
) (map[string]interface{}, error) {
out := map[string]interface{}{}
for key, value := range item {
interpolatedValue, err := recursiveInterpolate(value, mapping)
interpolatedValue, err := recursiveInterpolate(value, opts)
switch err := err.(type) {
case nil:
case *template.InvalidTemplateError:
return nil, errors.Errorf(
"Invalid interpolation format for %#v option in %s %#v: %#v. You may need to escape any $ with another $.",
key, section, name, err.Template,
key, opts.SectionName, sectionkey, err.Template,
)
default:
return nil, errors.Wrapf(err, "error while interpolating %s in %s %s", key, section, name)
return nil, errors.Wrapf(err, "error while interpolating %s in %s %s", key, opts.SectionName, sectionkey)
}
out[key] = interpolatedValue
}
return out, nil
}
func recursiveInterpolate(
value interface{},
mapping template.Mapping,
) (interface{}, error) {
func recursiveInterpolate(value interface{}, opts Options) (interface{}, error) {
switch value := value.(type) {
case string:
return template.Substitute(value, mapping)
return template.Substitute(value, template.Mapping(opts.LookupValue))
case map[string]interface{}:
out := map[string]interface{}{}
for key, elem := range value {
interpolatedElem, err := recursiveInterpolate(elem, mapping)
interpolatedElem, err := recursiveInterpolate(elem, opts)
if err != nil {
return nil, err
}
@ -80,7 +93,7 @@ func recursiveInterpolate(
case []interface{}:
out := make([]interface{}, len(value))
for i, elem := range value {
interpolatedElem, err := recursiveInterpolate(elem, mapping)
interpolatedElem, err := recursiveInterpolate(elem, opts)
if err != nil {
return nil, err
}
@ -92,5 +105,4 @@ func recursiveInterpolate(
return value, nil
}
}

View File

@ -3,6 +3,7 @@ package interpolation
import (
"testing"
"github.com/gotestyourself/gotestyourself/env"
"github.com/stretchr/testify/assert"
)
@ -41,7 +42,10 @@ func TestInterpolate(t *testing.T) {
},
},
}
result, err := Interpolate(services, "service", defaultMapping)
result, err := Interpolate(services, Options{
SectionName: "service",
LookupValue: defaultMapping,
})
assert.NoError(t, err)
assert.Equal(t, expected, result)
}
@ -52,6 +56,27 @@ func TestInvalidInterpolation(t *testing.T) {
"image": "${",
},
}
_, err := Interpolate(services, "service", defaultMapping)
_, err := Interpolate(services, Options{
SectionName: "service",
LookupValue: defaultMapping,
})
assert.EqualError(t, err, `Invalid interpolation format for "image" option in service "servicea": "${". You may need to escape any $ with another $.`)
}
func TestInterpolateWithDefaults(t *testing.T) {
defer env.Patch(t, "FOO", "BARZ")()
config := map[string]interface{}{
"networks": map[string]interface{}{
"foo": "thing_${FOO}",
},
}
expected := map[string]interface{}{
"networks": map[string]interface{}{
"foo": "thing_BARZ",
},
}
result, err := Interpolate(config, Options{})
assert.NoError(t, err)
assert.Equal(t, expected, result)
}