From 0aa7ca943c0c84cdc207bec602c28c8cf16f6f83 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 3 Oct 2017 16:22:02 -0400 Subject: [PATCH] Update interface of Interpolate Signed-off-by: Daniel Nephin --- cli/compose/interpolation/interpolation.go | 58 +++++++++++-------- .../interpolation/interpolation_test.go | 29 +++++++++- 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/cli/compose/interpolation/interpolation.go b/cli/compose/interpolation/interpolation.go index 38673c5c00..4cb65d706d 100644 --- a/cli/compose/interpolation/interpolation.go +++ b/cli/compose/interpolation/interpolation.go @@ -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 } - } diff --git a/cli/compose/interpolation/interpolation_test.go b/cli/compose/interpolation/interpolation_test.go index 9b055f4703..08de5c83e0 100644 --- a/cli/compose/interpolation/interpolation_test.go +++ b/cli/compose/interpolation/interpolation_test.go @@ -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) +}