diff --git a/terraform/eval_provider.go b/terraform/eval_provider.go index 61efcc235..092fd18d8 100644 --- a/terraform/eval_provider.go +++ b/terraform/eval_provider.go @@ -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 @@ -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{}{}) } diff --git a/terraform/eval_provider_test.go b/terraform/eval_provider_test.go index 5719b62c5..40af542e2 100644 --- a/terraform/eval_provider_test.go +++ b/terraform/eval_provider_test.go @@ -3,6 +3,8 @@ package terraform import ( "reflect" "testing" + + "github.com/hashicorp/terraform/config" ) func TestEvalBuildProviderConfig_impl(t *testing.T) { @@ -10,7 +12,11 @@ func TestEvalBuildProviderConfig_impl(t *testing.T) { } 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{ @@ -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) } } @@ -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) + } +}