Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: Input walk shouldn't clobber dynamic provider config #13264

Merged
merged 1 commit into from
Apr 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion terraform/eval_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ func (n *EvalBuildProviderConfig) Eval(ctx EvalContext) (interface{}, error) {

// If we have a configuration set, then merge that in
if input := ctx.ProviderInput(n.Provider); input != nil {
// "input" is a map of the subset of config values that were known
// during the input walk, set by EvalInputProvider. Note that
// in particular it does *not* include attributes that had
// computed values at input time; those appear *only* in
// "cfg" here.
rc, err := config.NewRawConfig(input)
if err != nil {
return nil, err
Expand Down Expand Up @@ -136,7 +141,21 @@ func (n *EvalInputProvider) Eval(ctx EvalContext) (interface{}, error) {
// Set the input that we received so that child modules don't attempt
// to ask for input again.
if config != nil && len(config.Config) > 0 {
ctx.SetProviderInput(n.Name, config.Config)
// This repository of provider input results on the context doesn't
// retain config.ComputedKeys, so we need to filter those out here
// in order that later users of this data won't try to use the unknown
// value placeholder as if it were a literal value. This map is just
// of known values we've been able to complete so far; dynamic stuff
// will be merged in by EvalBuildProviderConfig on subsequent
// (post-input) walks.
confMap := config.Config
if config.ComputedKeys != nil {
for _, key := range config.ComputedKeys {
delete(confMap, key)
}
}

ctx.SetProviderInput(n.Name, confMap)
} else {
ctx.SetProviderInput(n.Name, map[string]interface{}{})
}
Expand Down
92 changes: 86 additions & 6 deletions terraform/eval_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ package terraform
import (
"reflect"
"testing"

"github.com/hashicorp/terraform/config"
)

func TestEvalBuildProviderConfig_impl(t *testing.T) {
var _ EvalNode = new(EvalBuildProviderConfig)
}

func TestEvalBuildProviderConfig(t *testing.T) {
config := testResourceConfig(t, map[string]interface{}{})
config := testResourceConfig(t, map[string]interface{}{
"set_in_config": "config",
"set_in_config_and_parent": "config",
"computed_in_config": "config",
})
provider := "foo"

n := &EvalBuildProviderConfig{
Expand All @@ -21,22 +27,33 @@ func TestEvalBuildProviderConfig(t *testing.T) {

ctx := &MockEvalContext{
ParentProviderConfigConfig: testResourceConfig(t, map[string]interface{}{
"foo": "bar",
"inherited_from_parent": "parent",
"set_in_config_and_parent": "parent",
}),
ProviderInputConfig: map[string]interface{}{
"bar": "baz",
"set_in_config": "input",
"set_by_input": "input",
},
}
if _, err := n.Eval(ctx); err != nil {
t.Fatalf("err: %s", err)
}

// This is a merger of the following, with later items taking precedence:
// - "config" (the config as written in the current module, with all
// interpolation expressions resolved)
// - ProviderInputConfig (mock of config produced by the input walk, after
// prompting the user interactively for values unspecified in config)
// - ParentProviderConfigConfig (mock of config inherited from a parent module)
expected := map[string]interface{}{
"foo": "bar",
"bar": "baz",
"set_in_config": "input", // in practice, input map contains identical literals from config
"set_in_config_and_parent": "parent",
"inherited_from_parent": "parent",
"computed_in_config": "config",
"set_by_input": "input",
}
if !reflect.DeepEqual(config.Raw, expected) {
t.Fatalf("bad: %#v", config.Raw)
t.Fatalf("incorrect merged config %#v; want %#v", config.Raw, expected)
}
}

Expand Down Expand Up @@ -151,3 +168,66 @@ func TestEvalGetProvider(t *testing.T) {
t.Fatalf("bad: %#v", ctx.ProviderName)
}
}

func TestEvalInputProvider(t *testing.T) {
var provider ResourceProvider = &MockResourceProvider{
InputFn: func(ui UIInput, c *ResourceConfig) (*ResourceConfig, error) {
if c.Config["mock_config"] != "mock" {
t.Fatalf("original config not passed to provider.Input")
}

rawConfig, err := config.NewRawConfig(map[string]interface{}{
"set_in_config": "input",
"set_by_input": "input",
"computed": "fake_computed",
})
if err != nil {
return nil, err
}
config := NewResourceConfig(rawConfig)
config.ComputedKeys = []string{"computed"} // fake computed key

return config, nil
},
}
ctx := &MockEvalContext{ProviderProvider: provider}
rawConfig, err := config.NewRawConfig(map[string]interface{}{
"mock_config": "mock",
})
if err != nil {
t.Fatalf("NewRawConfig failed: %s", err)
}
config := NewResourceConfig(rawConfig)

n := &EvalInputProvider{
Name: "mock",
Provider: &provider,
Config: &config,
}

result, err := n.Eval(ctx)
if err != nil {
t.Fatalf("Eval failed: %s", err)
}
if result != nil {
t.Fatalf("Eval returned non-nil result %#v", result)
}

if !ctx.SetProviderInputCalled {
t.Fatalf("ctx.SetProviderInput wasn't called")
}

if got, want := ctx.SetProviderInputName, "mock"; got != want {
t.Errorf("wrong provider name %q; want %q", got, want)
}

inputCfg := ctx.SetProviderInputConfig
want := map[string]interface{}{
"set_in_config": "input",
"set_by_input": "input",
// "computed" is omitted because it value isn't known at input time
}
if !reflect.DeepEqual(inputCfg, want) {
t.Errorf("got incorrect input config %#v; want %#v", inputCfg, want)
}
}