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 package interpolation
import ( import (
"os"
"github.com/docker/cli/cli/compose/template" "github.com/docker/cli/cli/compose/template"
"github.com/pkg/errors" "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 // 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{}{} 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 { if item == nil {
out[name] = nil out[key] = nil
continue continue
} }
mapItem, ok := item.(map[string]interface{}) mapItem, ok := item.(map[string]interface{})
if !ok { 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 { if err != nil {
return nil, err return nil, err
} }
out[name] = interpolatedItem out[key] = interpolatedItem
} }
return out, nil return out, nil
} }
func interpolateSectionItem( func interpolateSectionItem(
name string, sectionkey string,
item map[string]interface{}, item map[string]interface{},
section string, opts Options,
mapping template.Mapping,
) (map[string]interface{}, error) { ) (map[string]interface{}, error) {
out := map[string]interface{}{} out := map[string]interface{}{}
for key, value := range item { for key, value := range item {
interpolatedValue, err := recursiveInterpolate(value, mapping) interpolatedValue, err := recursiveInterpolate(value, opts)
switch err := err.(type) { switch err := err.(type) {
case nil: case nil:
case *template.InvalidTemplateError: case *template.InvalidTemplateError:
return nil, errors.Errorf( return nil, errors.Errorf(
"Invalid interpolation format for %#v option in %s %#v: %#v. You may need to escape any $ with another $.", "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: 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 out[key] = interpolatedValue
} }
return out, nil return out, nil
} }
func recursiveInterpolate( func recursiveInterpolate(value interface{}, opts Options) (interface{}, error) {
value interface{},
mapping template.Mapping,
) (interface{}, error) {
switch value := value.(type) { switch value := value.(type) {
case string: case string:
return template.Substitute(value, mapping) return template.Substitute(value, template.Mapping(opts.LookupValue))
case map[string]interface{}: case map[string]interface{}:
out := map[string]interface{}{} out := map[string]interface{}{}
for key, elem := range value { for key, elem := range value {
interpolatedElem, err := recursiveInterpolate(elem, mapping) interpolatedElem, err := recursiveInterpolate(elem, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -80,7 +93,7 @@ func recursiveInterpolate(
case []interface{}: case []interface{}:
out := make([]interface{}, len(value)) out := make([]interface{}, len(value))
for i, elem := range value { for i, elem := range value {
interpolatedElem, err := recursiveInterpolate(elem, mapping) interpolatedElem, err := recursiveInterpolate(elem, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -92,5 +105,4 @@ func recursiveInterpolate(
return value, nil return value, nil
} }
} }

View File

@ -3,6 +3,7 @@ package interpolation
import ( import (
"testing" "testing"
"github.com/gotestyourself/gotestyourself/env"
"github.com/stretchr/testify/assert" "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.NoError(t, err)
assert.Equal(t, expected, result) assert.Equal(t, expected, result)
} }
@ -52,6 +56,27 @@ func TestInvalidInterpolation(t *testing.T) {
"image": "${", "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 $.`) 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)
}