terraform: change the graph a bit to better support providers with

modules

This doesn't cause inheritence to work yet. That is coming
This commit is contained in:
Mitchell Hashimoto 2014-09-24 13:31:35 -07:00
parent 86a4a6c7c8
commit 8dbc7e0ccb
8 changed files with 269 additions and 128 deletions

View File

@ -163,7 +163,11 @@ func (g *Graph) String() string {
} }
sort.Strings(keys) sort.Strings(keys)
buf.WriteString(fmt.Sprintf("root: %s\n", g.Root.Name)) if g.Root != nil {
buf.WriteString(fmt.Sprintf("root: %s\n", g.Root.Name))
} else {
buf.WriteString("root: <unknown>\n")
}
for _, k := range keys { for _, k := range keys {
n := mapping[k] n := mapping[k]
buf.WriteString(fmt.Sprintf("%s\n", n.Name)) buf.WriteString(fmt.Sprintf("%s\n", n.Name))

View File

@ -353,14 +353,16 @@ func (c *Context) validateWalkFn(rws *[]string, res *[]error) depgraph.WalkFunc
} }
case *GraphNodeResourceProvider: case *GraphNodeResourceProvider:
sharedProvider := rn.Provider
var raw *config.RawConfig var raw *config.RawConfig
if rn.Config != nil { if sharedProvider.Config != nil {
raw = rn.Config.RawConfig raw = sharedProvider.Config.RawConfig
} }
rc := NewResourceConfig(raw) rc := NewResourceConfig(raw)
for k, p := range rn.Providers { for k, p := range sharedProvider.Providers {
log.Printf("[INFO] Validating provider: %s", k) log.Printf("[INFO] Validating provider: %s", k)
ws, es := p.Validate(rc) ws, es := p.Validate(rc)
for i, w := range ws { for i, w := range ws {
@ -903,16 +905,18 @@ func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc {
// Skip it // Skip it
return nil return nil
case *GraphNodeResourceProvider: case *GraphNodeResourceProvider:
sharedProvider := m.Provider
// Interpolate in the variables and configure all the providers // Interpolate in the variables and configure all the providers
var raw *config.RawConfig var raw *config.RawConfig
if m.Config != nil { if sharedProvider.Config != nil {
raw = m.Config.RawConfig raw = sharedProvider.Config.RawConfig
} }
rc := NewResourceConfig(raw) rc := NewResourceConfig(raw)
rc.interpolate(c) rc.interpolate(c)
for k, p := range m.Providers { for k, p := range sharedProvider.Providers {
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

@ -4,7 +4,9 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
"sync"
"testing" "testing"
"sort"
) )
func TestContextGraph(t *testing.T) { func TestContextGraph(t *testing.T) {
@ -1589,6 +1591,60 @@ func TestContextPlan_moduleOrphans(t *testing.T) {
} }
} }
func TestContextPlan_moduleProviderInherit(t *testing.T) {
t.Skip()
var l sync.Mutex
var ps []*MockResourceProvider
var calls []string
m := testModule(t, "plan-module-provider-inherit")
ctx := testContext(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": func() (ResourceProvider, error) {
l.Lock()
defer l.Unlock()
p := testProvider("aws")
p.ConfigureFn = func(c *ResourceConfig) error {
if v, ok := c.Get("from"); !ok || v.(string) != "root" {
return fmt.Errorf("bad")
}
return nil
}
p.DiffFn = func(
info *InstanceInfo,
state *InstanceState,
c *ResourceConfig) (*InstanceDiff, error) {
v, _ := c.Get("from")
calls = append(calls, v.(string))
return testDiffFn(info, state, c)
}
ps = append(ps, p)
return p, nil
},
},
})
_, err := ctx.Plan(nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if len(ps) != 2 {
t.Fatalf("bad: %#v", ps)
}
actual := calls
sort.Strings(actual)
expected := []string{"child", "root"}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestContextPlan_moduleVar(t *testing.T) { func TestContextPlan_moduleVar(t *testing.T) {
m := testModule(t, "plan-module-var") m := testModule(t, "plan-module-var")
p := testProvider("aws") p := testProvider("aws")

View File

@ -58,6 +58,10 @@ type GraphOpts struct {
// Provisioners is a mapping of names to a resource provisioner. // Provisioners is a mapping of names to a resource provisioner.
// These must be provided to support resource provisioners. // These must be provided to support resource provisioners.
Provisioners map[string]ResourceProvisionerFactory Provisioners map[string]ResourceProvisionerFactory
// parent specifies the parent graph if there is one. This should not be
// set manually.
parent *depgraph.Graph
} }
// GraphRootNode is the name of the root node in the Terraform resource // GraphRootNode is the name of the root node in the Terraform resource
@ -77,10 +81,10 @@ type GraphNodeModule struct {
// this represents a _single_, _resource_ to be managed, not a set of resources // this represents a _single_, _resource_ to be managed, not a set of resources
// or a component of a resource. // or a component of a resource.
type GraphNodeResource struct { type GraphNodeResource struct {
Index int Index int
Config *config.Resource Config *config.Resource
Resource *Resource Resource *Resource
ResourceProviderID string ResourceProviderNode string
} }
// GraphNodeResourceMeta is a node type in the graph that represents the // GraphNodeResourceMeta is a node type in the graph that represents the
@ -96,10 +100,17 @@ type GraphNodeResourceMeta struct {
// GraphNodeResourceProvider is a node type in the graph that represents // GraphNodeResourceProvider is a node type in the graph that represents
// the configuration for a resource provider. // the configuration for a resource provider.
type GraphNodeResourceProvider struct { type GraphNodeResourceProvider struct {
ID string ID string
Provider *graphSharedProvider
}
// graphSharedProvider is a structure that stores a configuration
// with initialized providers and might be shared across different
// graphs in order to have only one instance of a provider.
type graphSharedProvider struct {
Config *config.ProviderConfig
Providers map[string]ResourceProvider Providers map[string]ResourceProvider
ProviderKeys []string ProviderKeys []string
Config *config.ProviderConfig
} }
// Graph builds a dependency graph of all the resources for infrastructure // Graph builds a dependency graph of all the resources for infrastructure
@ -160,51 +171,30 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
// and not "orphans" (that are in the state, but not in the config). // and not "orphans" (that are in the state, but not in the config).
graphAddConfigResources(g, conf, modState) graphAddConfigResources(g, conf, modState)
// Add the modules that are in the configuration.
if err := graphAddConfigModules(g, conf, opts); err != nil {
return nil, err
}
// Add explicit dependsOn dependencies to the graph
graphAddExplicitDeps(g)
if modState != nil { if modState != nil {
// Next, add the state orphans if we have any // Next, add the state orphans if we have any
graphAddOrphans(g, conf, modState) graphAddOrphans(g, conf, modState)
// Add tainted resources if we have any. // Add tainted resources if we have any.
graphAddTainted(g, modState) graphAddTainted(g, modState)
} }
if opts.State != nil { // Create the resource provider nodes for explicitly configured
// Add module orphans if we have any of those // providers within our graph.
if ms := opts.State.Children(opts.ModulePath); len(ms) > 0 { graphAddConfigProviderConfigs(g, conf)
if err := graphAddModuleOrphans(g, conf, ms, opts); err != nil {
return nil, err if opts.parent != nil {
} // Add/merge the provider configurations from the parent so that
} // we properly "inherit" providers.
graphAddParentProviderConfigs(g, opts.parent)
} }
// Map the provider configurations to all of the resources // First pass matching resources to providers. This will allow us to
graphAddProviderConfigs(g, conf) // determine what providers are missing.
graphMapResourceProviderId(g)
// Setup the provisioners. These may have variable dependencies,
// and must be done before dependency setup
if err := graphMapResourceProvisioners(g, opts.Provisioners); err != nil {
return nil, err
}
// Add all the variable dependencies
graphAddVariableDeps(g)
// Build the root so that we have a single valid root
graphAddRoot(g)
// If providers were given, lets associate the proper providers and
// instantiate them.
if len(opts.Providers) > 0 { if len(opts.Providers) > 0 {
// Add missing providers from the mapping // Add missing providers from the mapping.
if err := graphAddMissingResourceProviders(g, opts.Providers); err != nil { if err := graphAddMissingResourceProviders(g, opts.Providers); err != nil {
return nil, err return nil, err
} }
@ -220,6 +210,38 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
} }
} }
// Add the modules that are in the configuration.
if err := graphAddConfigModules(g, conf, opts); err != nil {
return nil, err
}
if opts.State != nil {
// Add module orphans if we have any of those
if ms := opts.State.Children(opts.ModulePath); len(ms) > 0 {
if err := graphAddModuleOrphans(g, conf, ms, opts); err != nil {
return nil, err
}
}
}
// Add the provider dependencies
graphAddResourceProviderDeps(g)
// Add explicit dependsOn dependencies to the graph
graphAddExplicitDeps(g)
// Setup the provisioners. These may have variable dependencies,
// and must be done before dependency setup
if err := graphMapResourceProvisioners(g, opts.Provisioners); err != nil {
return nil, err
}
// Add all the variable dependencies
graphAddVariableDeps(g)
// Build the root so that we have a single valid root
graphAddRoot(g)
// If we have a diff, then make sure to add that in // If we have a diff, then make sure to add that in
if modDiff != nil { if modDiff != nil {
if err := graphAddDiff(g, modDiff); err != nil { if err := graphAddDiff(g, modDiff); err != nil {
@ -296,7 +318,7 @@ func graphAddConfigModules(
// Build the list of nouns to add to the graph // Build the list of nouns to add to the graph
nounsList := make([]*depgraph.Noun, 0, len(c.Modules)) nounsList := make([]*depgraph.Noun, 0, len(c.Modules))
for _, m := range c.Modules { for _, m := range c.Modules {
if n, err := graphModuleNoun(m.Name, m, opts); err != nil { if n, err := graphModuleNoun(m.Name, m, g, opts); err != nil {
return err return err
} else { } else {
nounsList = append(nounsList, n) nounsList = append(nounsList, n)
@ -590,7 +612,7 @@ func graphAddExplicitDeps(g *depgraph.Graph) {
} }
rs[rn.Resource.Id] = n rs[rn.Resource.Id] = n
if len(rn.Config.DependsOn) > 0 { if rn.Config != nil && len(rn.Config.DependsOn) > 0 {
depends = true depends = true
} }
} }
@ -632,7 +654,7 @@ func graphAddMissingResourceProviders(
if !ok { if !ok {
continue continue
} }
if rn.ResourceProviderID != "" { if rn.ResourceProviderNode != "" {
continue continue
} }
@ -647,28 +669,20 @@ func graphAddMissingResourceProviders(
// The resource provider ID is simply the shortest matching // The resource provider ID is simply the shortest matching
// prefix, since that'll give us the most resource providers // prefix, since that'll give us the most resource providers
// to choose from. // to choose from.
rn.ResourceProviderID = prefixes[len(prefixes)-1] id := prefixes[len(prefixes)-1]
rn.ResourceProviderNode = fmt.Sprintf("provider.%s", id)
// If we don't have a matching noun for this yet, insert it. // If we don't have a matching noun for this yet, insert it.
pn := g.Noun(fmt.Sprintf("provider.%s", rn.ResourceProviderID)) if g.Noun(rn.ResourceProviderNode) == nil {
if pn == nil { pn := &depgraph.Noun{
pn = &depgraph.Noun{ Name: rn.ResourceProviderNode,
Name: fmt.Sprintf("provider.%s", rn.ResourceProviderID),
Meta: &GraphNodeResourceProvider{ Meta: &GraphNodeResourceProvider{
ID: rn.ResourceProviderID, ID: id,
Config: nil, Provider: new(graphSharedProvider),
}, },
} }
g.Nouns = append(g.Nouns, pn) g.Nouns = append(g.Nouns, pn)
} }
// Add the provider configuration noun as a dependency
dep := &depgraph.Dependency{
Name: pn.Name,
Source: n,
Target: pn,
}
n.Deps = append(n.Deps, dep)
} }
if len(errs) > 0 { if len(errs) > 0 {
@ -699,7 +713,7 @@ func graphAddModuleOrphans(
continue continue
} }
if n, err := graphModuleNoun(k, nil, opts); err != nil { if n, err := graphModuleNoun(k, nil, g, opts); err != nil {
return err return err
} else { } else {
nounsList = append(nounsList, n) nounsList = append(nounsList, n)
@ -774,60 +788,28 @@ func graphAddOrphans(g *depgraph.Graph, c *config.Config, mod *ModuleState) {
} }
} }
// graphAddProviderConfigs cycles through all the resource-like nodes // graphAddParentProviderConfigs goes through and adds/merges provider
// and adds the provider configuration nouns into the tree. // configurations from the parent.
func graphAddProviderConfigs(g *depgraph.Graph, c *config.Config) { func graphAddParentProviderConfigs(g, parent *depgraph.Graph) {
nounsList := make([]*depgraph.Noun, 0, 2) }
pcNouns := make(map[string]*depgraph.Noun)
for _, noun := range g.Nouns {
resourceNode, ok := noun.Meta.(*GraphNodeResource)
if !ok {
continue
}
// Look up the provider config for this resource // graphAddConfigProviderConfigs adds a GraphNodeResourceProvider for every
pcName := config.ProviderConfigName( // `provider` configuration block. Note that a provider may exist that
resourceNode.Resource.Info.Type, c.ProviderConfigs) // isn't used for any resources. These will be pruned later.
if pcName == "" { func graphAddConfigProviderConfigs(g *depgraph.Graph, c *config.Config) {
continue nounsList := make([]*depgraph.Noun, 0, len(c.ProviderConfigs))
} for _, pc := range c.ProviderConfigs {
noun := &depgraph.Noun{
// We have one, so build the noun if it hasn't already been made Name: fmt.Sprintf("provider.%s", pc.Name),
pcNoun, ok := pcNouns[pcName] Meta: &GraphNodeResourceProvider{
if !ok { ID: pc.Name,
var pc *config.ProviderConfig Provider: &graphSharedProvider{
for _, v := range c.ProviderConfigs {
if v.Name == pcName {
pc = v
break
}
}
if pc == nil {
panic("pc not found")
}
pcNoun = &depgraph.Noun{
Name: fmt.Sprintf("provider.%s", pcName),
Meta: &GraphNodeResourceProvider{
ID: pcName,
Config: pc, Config: pc,
}, },
} },
pcNouns[pcName] = pcNoun
nounsList = append(nounsList, pcNoun)
} }
// Set the resource provider ID for this noun so we can look it nounsList = append(nounsList, noun)
// up later easily.
resourceNode.ResourceProviderID = pcName
// Add the provider configuration noun as a dependency
dep := &depgraph.Dependency{
Name: pcName,
Source: noun,
Target: pcNoun,
}
noun.Deps = append(noun.Deps, dep)
} }
// Add all the provider config nouns to the graph // Add all the provider config nouns to the graph
@ -890,8 +872,10 @@ func graphAddVariableDeps(g *depgraph.Graph) {
} }
case *GraphNodeResourceProvider: case *GraphNodeResourceProvider:
vars := m.Config.RawConfig.Variables if m.Provider != nil && m.Provider.Config != nil {
nounAddVariableDeps(g, n, vars, false) vars := m.Provider.Config.RawConfig.Variables
nounAddVariableDeps(g, n, vars, false)
}
default: default:
// Other node types don't have dependencies or we don't support it // Other node types don't have dependencies or we don't support it
@ -963,7 +947,8 @@ func graphAddTainted(g *depgraph.Graph, mod *ModuleState) {
// graphModuleNoun creates a noun for a module. // graphModuleNoun creates a noun for a module.
func graphModuleNoun( func graphModuleNoun(
n string, m *config.Module, opts *GraphOpts) (*depgraph.Noun, error) { n string, m *config.Module,
g *depgraph.Graph, opts *GraphOpts) (*depgraph.Noun, error) {
name := fmt.Sprintf("module.%s", n) name := fmt.Sprintf("module.%s", n)
path := make([]string, len(opts.ModulePath)+1) path := make([]string, len(opts.ModulePath)+1)
copy(path, opts.ModulePath) copy(path, opts.ModulePath)
@ -972,6 +957,7 @@ func graphModuleNoun(
// Build the opts we'll use to make the next graph // Build the opts we'll use to make the next graph
subOpts := *opts subOpts := *opts
subOpts.ModulePath = path subOpts.ModulePath = path
subOpts.parent = g
subGraph, err := Graph(&subOpts) subGraph, err := Graph(&subOpts)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
@ -1063,10 +1049,12 @@ func graphInitResourceProviders(
} }
} }
sharedProvider := rn.Provider
// Go through each prefix and instantiate if necessary, then // Go through each prefix and instantiate if necessary, then
// verify if this provider is of use to us or not. // verify if this provider is of use to us or not.
rn.Providers = make(map[string]ResourceProvider) sharedProvider.Providers = make(map[string]ResourceProvider)
rn.ProviderKeys = prefixes sharedProvider.ProviderKeys = prefixes
for _, prefix := range prefixes { for _, prefix := range prefixes {
p, err := ps[prefix]() p, err := ps[prefix]()
if err != nil { if err != nil {
@ -1081,11 +1069,11 @@ func graphInitResourceProviders(
continue continue
} }
rn.Providers[prefix] = p sharedProvider.Providers[prefix] = p
} }
// If we never found a provider, then error and continue // If we never found a provider, then error and continue
if len(rn.Providers) == 0 { if len(sharedProvider.Providers) == 0 {
errs = append(errs, fmt.Errorf( errs = append(errs, fmt.Errorf(
"Provider for configuration '%s' not found.", "Provider for configuration '%s' not found.",
rn.ID)) rn.ID))
@ -1100,6 +1088,74 @@ func graphInitResourceProviders(
return nil return nil
} }
// graphAddResourceProviderDeps goes through all the nodes in the graph
// and adds any dependencies to resource providers as needed.
func graphAddResourceProviderDeps(g *depgraph.Graph) {
for _, rawN := range g.Nouns {
switch n := rawN.Meta.(type) {
case *GraphNodeResource:
// Not sure how this would happen, but we might as well
// check for it.
if n.ResourceProviderNode == "" {
continue
}
// Get the noun this depends on.
target := g.Noun(n.ResourceProviderNode)
// Create the dependency to the provider
dep := &depgraph.Dependency{
Name: target.Name,
Source: rawN,
Target: target,
}
rawN.Deps = append(rawN.Deps, dep)
}
}
}
// graphMapResourceProviderId goes through the graph and maps the
// ID of a resource provider node to each resource. This lets us know which
// configuration is for which resource.
//
// This is safe to call multiple times.
func graphMapResourceProviderId(g *depgraph.Graph) {
// Build the list of provider configs we have
ps := make(map[string]string)
for _, n := range g.Nouns {
pn, ok := n.Meta.(*GraphNodeResourceProvider)
if !ok {
continue
}
ps[n.Name] = pn.ID
}
// Go through every resource and find the shortest matching provider
for _, n := range g.Nouns {
rn, ok := n.Meta.(*GraphNodeResource)
if !ok {
continue
}
var match, matchNode string
for n, p := range ps {
if !strings.HasPrefix(rn.Resource.Info.Type, p) {
continue
}
if len(p) > len(match) {
match = p
matchNode = n
}
}
if matchNode == "" {
continue
}
rn.ResourceProviderNode = matchNode
}
}
// graphMapResourceProviders takes a graph that already has initialized // graphMapResourceProviders takes a graph that already has initialized
// the resource providers (using graphInitResourceProviders) and maps the // the resource providers (using graphInitResourceProviders) and maps the
// resource providers to the resources themselves. // resource providers to the resources themselves.
@ -1124,24 +1180,25 @@ func graphMapResourceProviders(g *depgraph.Graph) error {
continue continue
} }
rpn, ok := mapping[rn.ResourceProviderID] rpnRaw := g.Noun(rn.ResourceProviderNode)
if !ok { if rpnRaw == nil {
// This should never happen since when building the graph // This should never happen since when building the graph
// we ensure that everything matches up. // we ensure that everything matches up.
panic(fmt.Sprintf( panic(fmt.Sprintf(
"Resource provider ID not found: %s (type: %s)", "Resource provider not found: %s (type: %s)",
rn.ResourceProviderID, rn.ResourceProviderNode,
rn.Resource.Info.Type)) rn.Resource.Info.Type))
} }
rpn := rpnRaw.Meta.(*GraphNodeResourceProvider)
var provider ResourceProvider var provider ResourceProvider
for _, k := range rpn.ProviderKeys { for _, k := range rpn.Provider.ProviderKeys {
// Only try this provider if it has the right prefix // Only try this provider if it has the right prefix
if !strings.HasPrefix(rn.Resource.Info.Type, k) { if !strings.HasPrefix(rn.Resource.Info.Type, k) {
continue continue
} }
rp := rpn.Providers[k] rp := rpn.Provider.Providers[k]
if ProviderSatisfies(rp, rn.Resource.Info.Type) { if ProviderSatisfies(rp, rn.Resource.Info.Type) {
provider = rp provider = rp
break break

View File

@ -310,7 +310,7 @@ func TestGraphFull(t *testing.T) {
t.Fatalf("bad: %#v", m) t.Fatalf("bad: %#v", m)
} }
case *GraphNodeResourceProvider: case *GraphNodeResourceProvider:
if len(m.Providers) == 0 { if len(m.Provider.Providers) == 0 {
t.Fatalf("bad: %#v", m) t.Fatalf("bad: %#v", m)
} }
default: default:

View File

@ -21,6 +21,7 @@ type MockResourceProvider struct {
ApplyReturnError error ApplyReturnError error
ConfigureCalled bool ConfigureCalled bool
ConfigureConfig *ResourceConfig ConfigureConfig *ResourceConfig
ConfigureFn func(*ResourceConfig) error
ConfigureReturnError error ConfigureReturnError error
DiffCalled bool DiffCalled bool
DiffInfo *InstanceInfo DiffInfo *InstanceInfo
@ -79,6 +80,11 @@ func (p *MockResourceProvider) Configure(c *ResourceConfig) error {
p.ConfigureCalled = true p.ConfigureCalled = true
p.ConfigureConfig = c p.ConfigureConfig = c
if p.ConfigureFn != nil {
return p.ConfigureFn(c)
}
return p.ConfigureReturnError return p.ConfigureReturnError
} }

View File

@ -0,0 +1,3 @@
resource "aws_instance" "foo" {
from = "child"
}

View File

@ -0,0 +1,11 @@
module "child" {
source = "./child"
}
provider "aws" {
from = "root"
}
resource "aws_instance" "foo" {
from = "root"
}