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.
type Context struct {
module *module.Tree
diff *Diff
hooks []Hook
state *State
providers map[string]ResourceProviderFactory
provisioners map[string]ResourceProvisionerFactory
variables map[string]string
uiInput UIInput
module *module.Tree
diff *Diff
hooks []Hook
state *State
providerConfig map[string]map[string]map[string]interface{}
providers map[string]ResourceProviderFactory
provisioners map[string]ResourceProvisionerFactory
variables map[string]string
uiInput UIInput
l sync.Mutex // Lock acquired during any task
parCh chan struct{} // Semaphore used to limit parallelism
@ -78,14 +79,15 @@ func NewContext(opts *ContextOpts) *Context {
parCh := make(chan struct{}, par)
return &Context{
diff: opts.Diff,
hooks: hooks,
module: opts.Module,
state: opts.State,
providers: opts.Providers,
provisioners: opts.Provisioners,
variables: opts.Variables,
uiInput: opts.UIInput,
diff: opts.Diff,
hooks: hooks,
module: opts.Module,
state: opts.State,
providerConfig: make(map[string]map[string]map[string]interface{}),
providers: opts.Providers,
provisioners: opts.Provisioners,
variables: opts.Variables,
uiInput: opts.UIInput,
parCh: parCh,
sh: sh,
@ -548,31 +550,44 @@ func (c *walkContext) inputWalkFn() depgraph.WalkFunc {
// Resources don't matter for input. Continue.
return nil
case *GraphNodeResourceProvider:
return nil
/*
// If we already did this provider, then we're done.
meta.Lock()
_, ok := meta.Done[rn.ID]
meta.Unlock()
if ok {
return nil
}
// Acquire the lock the whole time so we only ask for input
// one at a time.
meta.Lock()
defer meta.Unlock()
// Get the raw configuration because this is what we
// pass into the API.
var raw *config.RawConfig
sharedProvider := rn.Provider
if sharedProvider.Config != nil {
raw = sharedProvider.Config.RawConfig
}
rc := NewResourceConfig(raw)
// If we already did this provider, then we're done.
if _, ok := meta.Done[rn.ID]; ok {
return nil
}
// Go through each provider and capture the input necessary
// to satisfy it.
for k, p := range sharedProvider.Providers {
ws, es := p.Validate(rc)
// Get the raw configuration because this is what we
// pass into the API.
var raw *config.RawConfig
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
@ -1116,47 +1131,17 @@ func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc {
case *GraphNodeResourceProvider:
sharedProvider := m.Provider
// Interpolate in the variables and configure all the providers
var raw *config.RawConfig
if sharedProvider.Config != nil {
raw = sharedProvider.Config.RawConfig
// Check if we have an override
cs, ok := c.Context.providerConfig[m.ID]
if !ok {
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 {
// 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)
err := p.Configure(rc)
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) {
m := testModule(t, "apply-good")
p := testProvider("aws")

View File

@ -119,7 +119,8 @@ type graphSharedProvider struct {
ProviderKeys []string
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
@ -1461,6 +1462,50 @@ func graphMapResourceProvisioners(g *depgraph.Graph,
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
// providers we know about by prefix and returns a list of prefixes
// that might be valid for that resource.

View File

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

View File

@ -17,6 +17,7 @@ type MockResourceProvider struct {
InputConfig *ResourceConfig
InputReturnConfig *ResourceConfig
InputReturnError error
InputFn func(UIInput, *ResourceConfig) (*ResourceConfig, error)
ApplyCalled bool
ApplyInfo *InstanceInfo
ApplyState *InstanceState
@ -61,6 +62,9 @@ func (p *MockResourceProvider) Input(
p.InputCalled = true
p.InputInput = input
p.InputConfig = c
if p.InputFn != nil {
return p.InputFn(input, c)
}
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
// 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 = `
aws_instance.bar:
ID = foo

View File

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