terraform: ask for input for providers

This commit is contained in:
Mitchell Hashimoto 2014-09-29 09:13:15 -07:00
parent caf8e372f2
commit 2791badf01
7 changed files with 173 additions and 81 deletions

View File

@ -25,14 +25,15 @@ type genericWalkFunc func(*walkContext, *Resource) error
// //
// Additionally, a context can be created from a Plan using Plan.Context. // Additionally, a context can be created from a Plan using Plan.Context.
type Context struct { type Context struct {
module *module.Tree module *module.Tree
diff *Diff diff *Diff
hooks []Hook hooks []Hook
state *State state *State
providers map[string]ResourceProviderFactory providerConfig map[string]map[string]map[string]interface{}
provisioners map[string]ResourceProvisionerFactory providers map[string]ResourceProviderFactory
variables map[string]string provisioners map[string]ResourceProvisionerFactory
uiInput UIInput variables map[string]string
uiInput UIInput
l sync.Mutex // Lock acquired during any task l sync.Mutex // Lock acquired during any task
parCh chan struct{} // Semaphore used to limit parallelism parCh chan struct{} // Semaphore used to limit parallelism
@ -78,14 +79,15 @@ func NewContext(opts *ContextOpts) *Context {
parCh := make(chan struct{}, par) parCh := make(chan struct{}, par)
return &Context{ return &Context{
diff: opts.Diff, diff: opts.Diff,
hooks: hooks, hooks: hooks,
module: opts.Module, module: opts.Module,
state: opts.State, state: opts.State,
providers: opts.Providers, providerConfig: make(map[string]map[string]map[string]interface{}),
provisioners: opts.Provisioners, providers: opts.Providers,
variables: opts.Variables, provisioners: opts.Provisioners,
uiInput: opts.UIInput, variables: opts.Variables,
uiInput: opts.UIInput,
parCh: parCh, parCh: parCh,
sh: sh, sh: sh,
@ -548,31 +550,44 @@ func (c *walkContext) inputWalkFn() depgraph.WalkFunc {
// Resources don't matter for input. Continue. // Resources don't matter for input. Continue.
return nil return nil
case *GraphNodeResourceProvider: case *GraphNodeResourceProvider:
return nil // Acquire the lock the whole time so we only ask for input
/* // one at a time.
// If we already did this provider, then we're done. meta.Lock()
meta.Lock() defer meta.Unlock()
_, ok := meta.Done[rn.ID]
meta.Unlock()
if ok {
return nil
}
// Get the raw configuration because this is what we // If we already did this provider, then we're done.
// pass into the API. if _, ok := meta.Done[rn.ID]; ok {
var raw *config.RawConfig return nil
sharedProvider := rn.Provider }
if sharedProvider.Config != nil {
raw = sharedProvider.Config.RawConfig
}
rc := NewResourceConfig(raw)
// Go through each provider and capture the input necessary // Get the raw configuration because this is what we
// to satisfy it. // pass into the API.
for k, p := range sharedProvider.Providers { var raw *config.RawConfig
ws, es := p.Validate(rc) sharedProvider := rn.Provider
if sharedProvider.Config != nil {
raw = sharedProvider.Config.RawConfig
}
rc := NewResourceConfig(raw)
// Go through each provider and capture the input necessary
// to satisfy it.
configs := make(map[string]map[string]interface{})
for k, p := range sharedProvider.Providers {
newc, err := p.Input(c.Context.uiInput, rc)
if err != nil {
return fmt.Errorf(
"Error configuring %s: %s", k, err)
} }
*/ if newc != nil {
configs[k] = newc.Raw
}
}
// Mark this provider as done
meta.Done[rn.ID] = struct{}{}
// Set the configuration
c.Context.providerConfig[rn.ID] = configs
} }
return nil return nil
@ -1116,47 +1131,17 @@ func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc {
case *GraphNodeResourceProvider: case *GraphNodeResourceProvider:
sharedProvider := m.Provider sharedProvider := m.Provider
// Interpolate in the variables and configure all the providers // Check if we have an override
var raw *config.RawConfig cs, ok := c.Context.providerConfig[m.ID]
if sharedProvider.Config != nil { if !ok {
raw = sharedProvider.Config.RawConfig cs = make(map[string]map[string]interface{})
} }
// If we have a parent, then merge in the parent configurations
// properly so we "inherit" the configurations.
if sharedProvider.Parent != nil {
var rawMap map[string]interface{}
if raw != nil {
rawMap = raw.Raw
}
parent := sharedProvider.Parent
for parent != nil {
if parent.Config != nil {
if rawMap == nil {
rawMap = parent.Config.RawConfig.Raw
}
for k, v := range parent.Config.RawConfig.Config() {
rawMap[k] = v
}
}
parent = parent.Parent
}
// Update our configuration to be the merged result
var err error
raw, err = config.NewRawConfig(rawMap)
if err != nil {
return fmt.Errorf("Error merging configurations: %s", err)
}
}
rc := NewResourceConfig(raw)
rc.interpolate(c)
for k, p := range sharedProvider.Providers { for k, p := range sharedProvider.Providers {
// Merge the configurations to get what we use to configure with
rc := sharedProvider.MergeConfig(false, cs[k])
rc.interpolate(c)
log.Printf("[INFO] Configuring provider: %s", k) log.Printf("[INFO] Configuring provider: %s", k)
err := p.Configure(rc) err := p.Configure(rc)
if err != nil { if err != nil {

View File

@ -460,6 +460,45 @@ func TestContextInput(t *testing.T) {
} }
} }
func TestContextInput_provider(t *testing.T) {
m := testModule(t, "input-provider")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
ctx := testContext(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
var actual interface{}
p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) {
c.Raw["foo"] = "bar"
return c, nil
}
p.ConfigureFn = func(c *ResourceConfig) error {
actual = c.Raw["foo"]
return nil
}
if err := ctx.Input(); err != nil {
t.Fatalf("err: %s", err)
}
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
if _, err := ctx.Apply(); err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(actual, "bar") {
t.Fatalf("bad: %#v", actual)
}
}
func TestContextApply(t *testing.T) { func TestContextApply(t *testing.T) {
m := testModule(t, "apply-good") m := testModule(t, "apply-good")
p := testProvider("aws") p := testProvider("aws")

View File

@ -119,7 +119,8 @@ type graphSharedProvider struct {
ProviderKeys []string ProviderKeys []string
Parent *graphSharedProvider Parent *graphSharedProvider
parentNoun *depgraph.Noun overrideConfig map[string]map[string]interface{}
parentNoun *depgraph.Noun
} }
// Graph builds a dependency graph of all the resources for infrastructure // Graph builds a dependency graph of all the resources for infrastructure
@ -1461,6 +1462,50 @@ func graphMapResourceProvisioners(g *depgraph.Graph,
return nil return nil
} }
// MergeConfig merges all the configurations in the proper order
// to result in the final configuration to use to configure this
// provider.
func (p *graphSharedProvider) MergeConfig(
raw bool, override map[string]interface{}) *ResourceConfig {
var rawMap map[string]interface{}
if override != nil {
rawMap = override
} else if p.Config != nil {
rawMap = p.Config.RawConfig.Raw
}
if rawMap == nil {
rawMap = make(map[string]interface{})
}
// Merge in all the parent configurations
if p.Parent != nil {
parent := p.Parent
for parent != nil {
if parent.Config != nil {
var merge map[string]interface{}
if raw {
merge = parent.Config.RawConfig.Raw
} else {
merge = parent.Config.RawConfig.Config()
}
for k, v := range merge {
rawMap[k] = v
}
}
parent = parent.Parent
}
}
rc, err := config.NewRawConfig(rawMap)
if err != nil {
panic("error building config: " + err.Error())
}
return NewResourceConfig(rc)
}
// matchingPrefixes takes a resource type and a set of resource // matchingPrefixes takes a resource type and a set of resource
// providers we know about by prefix and returns a list of prefixes // providers we know about by prefix and returns a list of prefixes
// that might be valid for that resource. // that might be valid for that resource.

View File

@ -199,11 +199,16 @@ func (c *ResourceConfig) interpolate(ctx *walkContext) error {
} }
} }
if c.raw != nil { if c.raw == nil {
c.ComputedKeys = c.raw.UnknownKeys() var err error
c.Raw = c.raw.Raw c.raw, err = config.NewRawConfig(make(map[string]interface{}))
c.Config = c.raw.Config() if err != nil {
return err
}
} }
c.ComputedKeys = c.raw.UnknownKeys()
c.Raw = c.raw.Raw
c.Config = c.raw.Config()
return nil return nil
} }

View File

@ -17,6 +17,7 @@ type MockResourceProvider struct {
InputConfig *ResourceConfig InputConfig *ResourceConfig
InputReturnConfig *ResourceConfig InputReturnConfig *ResourceConfig
InputReturnError error InputReturnError error
InputFn func(UIInput, *ResourceConfig) (*ResourceConfig, error)
ApplyCalled bool ApplyCalled bool
ApplyInfo *InstanceInfo ApplyInfo *InstanceInfo
ApplyState *InstanceState ApplyState *InstanceState
@ -61,6 +62,9 @@ func (p *MockResourceProvider) Input(
p.InputCalled = true p.InputCalled = true
p.InputInput = input p.InputInput = input
p.InputConfig = c p.InputConfig = c
if p.InputFn != nil {
return p.InputFn(input, c)
}
return p.InputReturnConfig, p.InputReturnError return p.InputReturnConfig, p.InputReturnError
} }

View File

@ -113,6 +113,19 @@ func (h *HookRecordApplyOrder) PreApply(
// Below are all the constant strings that are the expected output for // Below are all the constant strings that are the expected output for
// various tests. // various tests.
const testTerraformInputProviderStr = `
aws_instance.bar:
ID = foo
bar = override
foo = us-east-1
type = aws_instance
aws_instance.foo:
ID = foo
bar = baz
num = 2
type = aws_instance
`
const testTerraformInputVarsStr = ` const testTerraformInputVarsStr = `
aws_instance.bar: aws_instance.bar:
ID = foo ID = foo

View File

@ -0,0 +1 @@
resource "aws_instance" "foo" {}