diff --git a/terraform/context.go b/terraform/context.go index bca51d734a..34880eb5b2 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -57,8 +57,9 @@ type Context2 struct { uiInput UIInput variables map[string]string - l sync.Mutex // Lock acquired during any task - runCh <-chan struct{} + l sync.Mutex // Lock acquired during any task + providerInputConfig map[string]map[string]interface{} + runCh <-chan struct{} } // NewContext creates a new Context structure. @@ -81,15 +82,16 @@ func NewContext2(opts *ContextOpts) *Context2 { } return &Context2{ - diff: opts.Diff, - hooks: hooks, - module: opts.Module, - providers: opts.Providers, - provisioners: opts.Provisioners, - sh: sh, - state: state, - uiInput: opts.UIInput, - variables: opts.Variables, + diff: opts.Diff, + hooks: hooks, + module: opts.Module, + providers: opts.Providers, + providerInputConfig: make(map[string]map[string]interface{}), + provisioners: opts.Provisioners, + sh: sh, + state: state, + uiInput: opts.UIInput, + variables: opts.Variables, } } @@ -185,15 +187,12 @@ func (c *Context2) Input(mode InputMode) error { } } - /* - if mode&InputModeProvider != 0 { - // Create the walk context and walk the inputs, which will gather the - // inputs for any resource providers. - wc := c.walkContext(walkInput, rootModulePath) - wc.Meta = new(walkInputMeta) - return wc.Walk() + if mode&InputModeProvider != 0 { + // Do the walk + if _, err := c.walk(walkInput); err != nil { + return err } - */ + } return nil } diff --git a/terraform/context_test.go b/terraform/context_test.go index 1e31b5c225..0715c1d69f 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -2424,13 +2424,12 @@ func TestContext2Input(t *testing.T) { } } -/* -func TestContextInput_provider(t *testing.T) { +func TestContext2Input_provider(t *testing.T) { m := testModule(t, "input-provider") p := testProvider("aws") p.ApplyFn = testApplyFn p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ + ctx := testContext2(t, &ContextOpts{ Module: m, Providers: map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -2464,6 +2463,7 @@ func TestContextInput_provider(t *testing.T) { } } +/* func TestContextInput_providerId(t *testing.T) { input := new(MockUIInput) m := testModule(t, "input-provider") diff --git a/terraform/eval_context.go b/terraform/eval_context.go index 78fbd0d7bd..e1d4eef516 100644 --- a/terraform/eval_context.go +++ b/terraform/eval_context.go @@ -15,6 +15,9 @@ type EvalContext interface { // hook and should return the hook action to take and the error. Hook(func(Hook) (HookAction, error)) error + // Input is the UIInput object for interacting with the UI. + Input() UIInput + // InitProvider initializes the provider with the given name and // returns the implementation of the resource provider or an error. // @@ -32,6 +35,11 @@ type EvalContext interface { ConfigureProvider(string, *ResourceConfig) error ParentProviderConfig(string) *ResourceConfig + // ProviderInput and SetProviderInput are used to configure providers + // from user input. + ProviderInput(string) map[string]interface{} + SetProviderInput(string, map[string]interface{}) + // InitProvisioner initializes the provisioner with the given name and // returns the implementation of the resource provisioner or an error. // @@ -69,6 +77,9 @@ type MockEvalContext struct { HookCalled bool HookError error + InputCalled bool + InputInput UIInput + InitProviderCalled bool InitProviderName string InitProviderProvider ResourceProvider @@ -78,6 +89,14 @@ type MockEvalContext struct { ProviderName string ProviderProvider ResourceProvider + ProviderInputCalled bool + ProviderInputName string + ProviderInputConfig map[string]interface{} + + SetProviderInputCalled bool + SetProviderInputName string + SetProviderInputConfig map[string]interface{} + ConfigureProviderCalled bool ConfigureProviderName string ConfigureProviderConfig *ResourceConfig @@ -122,6 +141,11 @@ func (c *MockEvalContext) Hook(fn func(Hook) (HookAction, error)) error { return c.HookError } +func (c *MockEvalContext) Input() UIInput { + c.InputCalled = true + return c.InputInput +} + func (c *MockEvalContext) InitProvider(n string) (ResourceProvider, error) { c.InitProviderCalled = true c.InitProviderName = n @@ -147,6 +171,18 @@ func (c *MockEvalContext) ParentProviderConfig(n string) *ResourceConfig { return c.ParentProviderConfigConfig } +func (c *MockEvalContext) ProviderInput(n string) map[string]interface{} { + c.ProviderInputCalled = true + c.ProviderInputName = n + return c.ProviderInputConfig +} + +func (c *MockEvalContext) SetProviderInput(n string, cfg map[string]interface{}) { + c.SetProviderInputCalled = true + c.SetProviderInputName = n + c.SetProviderInputConfig = cfg +} + func (c *MockEvalContext) InitProvisioner(n string) (ResourceProvisioner, error) { c.InitProvisionerCalled = true c.InitProvisionerName = n diff --git a/terraform/eval_context_builtin.go b/terraform/eval_context_builtin.go index ea664f2fe0..6908cd620f 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -14,9 +14,11 @@ type BuiltinEvalContext struct { PathValue []string Interpolater *Interpolater Hooks []Hook + InputValue UIInput Providers map[string]ResourceProviderFactory ProviderCache map[string]ResourceProvider ProviderConfigCache map[string]*ResourceConfig + ProviderInputConfig map[string]map[string]interface{} ProviderLock *sync.Mutex Provisioners map[string]ResourceProvisionerFactory ProvisionerCache map[string]ResourceProvisioner @@ -49,6 +51,10 @@ func (ctx *BuiltinEvalContext) Hook(fn func(Hook) (HookAction, error)) error { return nil } +func (ctx *BuiltinEvalContext) Input() UIInput { + return ctx.InputValue +} + func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error) { ctx.once.Do(ctx.init) @@ -100,6 +106,20 @@ func (ctx *BuiltinEvalContext) ConfigureProvider( return p.Configure(cfg) } +func (ctx *BuiltinEvalContext) ProviderInput(n string) map[string]interface{} { + ctx.ProviderLock.Lock() + defer ctx.ProviderLock.Unlock() + + return ctx.ProviderInputConfig[n] +} + +func (ctx *BuiltinEvalContext) SetProviderInput(n string, c map[string]interface{}) { + ctx.ProviderLock.Lock() + defer ctx.ProviderLock.Unlock() + + ctx.ProviderInputConfig[n] = c +} + func (ctx *BuiltinEvalContext) ParentProviderConfig(n string) *ResourceConfig { ctx.ProviderLock.Lock() defer ctx.ProviderLock.Unlock() diff --git a/terraform/eval_provider.go b/terraform/eval_provider.go index 4d34b10336..22dac3f55f 100644 --- a/terraform/eval_provider.go +++ b/terraform/eval_provider.go @@ -2,6 +2,8 @@ package terraform import ( "fmt" + + "github.com/hashicorp/terraform/config" ) // EvalConfigProvider is an EvalNode implementation that configures @@ -17,15 +19,26 @@ func (n *EvalConfigProvider) Args() ([]EvalNode, []EvalType) { func (n *EvalConfigProvider) Eval( ctx EvalContext, args []interface{}) (interface{}, error) { - config := args[0].(*ResourceConfig) + cfg := args[0].(*ResourceConfig) + + // If we have a configuration set, then use that + if input := ctx.ProviderInput(n.Provider); input != nil { + rc, err := config.NewRawConfig(input) + if err != nil { + return nil, err + } + + merged := cfg.raw.Merge(rc) + cfg = NewResourceConfig(merged) + } // Get the parent configuration if there is one if parent := ctx.ParentProviderConfig(n.Provider); parent != nil { - merged := config.raw.Merge(parent.raw) - config = NewResourceConfig(merged) + merged := cfg.raw.Merge(parent.raw) + cfg = NewResourceConfig(merged) } - return nil, ctx.ConfigureProvider(n.Provider, config) + return nil, ctx.ConfigureProvider(n.Provider, cfg) } func (n *EvalConfigProvider) Type() EvalType { @@ -80,3 +93,52 @@ func (n *EvalGetProvider) Eval( func (n *EvalGetProvider) Type() EvalType { return EvalTypeResourceProvider } + +// EvalInputProvider is an EvalNode implementation that asks for input +// for the given provider configurations. +type EvalInputProvider struct { + Name string + Provider *ResourceProvider + Config *config.RawConfig +} + +func (n *EvalInputProvider) Args() ([]EvalNode, []EvalType) { + return nil, nil +} + +func (n *EvalInputProvider) Eval( + ctx EvalContext, args []interface{}) (interface{}, error) { + // If we already configured this provider, then don't do this again + if v := ctx.ProviderInput(n.Name); v != nil { + return nil, nil + } + + rc := NewResourceConfig(n.Config) + rc.Config = make(map[string]interface{}) + + // Wrap the input into a namespace + input := &PrefixUIInput{ + IdPrefix: fmt.Sprintf("provider.%s", n.Name), + QueryPrefix: fmt.Sprintf("provider.%s.", n.Name), + UIInput: ctx.Input(), + } + + // Go through each provider and capture the input necessary + // to satisfy it. + config, err := (*n.Provider).Input(input, rc) + if err != nil { + return nil, fmt.Errorf( + "Error configuring %s: %s", n.Name, err) + } + + if config != nil && len(config.Config) > 0 { + // Set the configuration + ctx.SetProviderInput(n.Name, config.Config) + } + + return nil, nil +} + +func (n *EvalInputProvider) Type() EvalType { + return EvalTypeNull +} diff --git a/terraform/evaltree_provider.go b/terraform/evaltree_provider.go index c5ba7dab6e..a4b3606b82 100644 --- a/terraform/evaltree_provider.go +++ b/terraform/evaltree_provider.go @@ -7,18 +7,45 @@ import ( // ProviderEvalTree returns the evaluation tree for initializing and // configuring providers. func ProviderEvalTree(n string, config *config.RawConfig) EvalNode { - return &EvalSequence{ - Nodes: []EvalNode{ - &EvalInitProvider{Name: n}, - &EvalValidateProvider{ - ProviderName: n, - Provider: &EvalGetProvider{Name: n}, - Config: &EvalInterpolate{Config: config}, - }, - &EvalConfigProvider{ - Provider: n, - Config: &EvalInterpolate{Config: config}, + seq := make([]EvalNode, 0, 5) + seq = append(seq, &EvalInitProvider{Name: n}) + + // Input stuff + var provider ResourceProvider + seq = append(seq, &EvalOpFilter{ + Ops: []walkOperation{walkInput}, + Node: &EvalSequence{ + Nodes: []EvalNode{ + &EvalGetProvider{ + Name: n, + Output: &provider, + }, + &EvalInputProvider{ + Name: n, + Provider: &provider, + Config: config, + }, }, }, - } + }) + + // Apply stuff + seq = append(seq, &EvalOpFilter{ + Ops: []walkOperation{walkValidate, walkRefresh, walkPlan, walkApply}, + Node: &EvalSequence{ + Nodes: []EvalNode{ + &EvalValidateProvider{ + ProviderName: n, + Provider: &EvalGetProvider{Name: n}, + Config: &EvalInterpolate{Config: config}, + }, + &EvalConfigProvider{ + Provider: n, + Config: &EvalInterpolate{Config: config}, + }, + }, + }, + }) + + return &EvalSequence{Nodes: seq} } diff --git a/terraform/graph_walk_context.go b/terraform/graph_walk_context.go index 3517cc3fd7..0f7b790922 100644 --- a/terraform/graph_walk_context.go +++ b/terraform/graph_walk_context.go @@ -55,9 +55,11 @@ func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext { ctx := &BuiltinEvalContext{ PathValue: g.Path, Hooks: w.Context.hooks, + InputValue: w.Context.uiInput, Providers: w.Context.providers, ProviderCache: w.providerCache, ProviderConfigCache: w.providerConfigCache, + ProviderInputConfig: w.Context.providerInputConfig, ProviderLock: &w.providerLock, Provisioners: w.Context.provisioners, ProvisionerCache: w.provisionerCache,