terraform: GraphNodeDependent

This commit is contained in:
Mitchell Hashimoto 2015-01-27 14:56:01 -08:00
parent 8dc4c56b2e
commit 8bf725e746
5 changed files with 102 additions and 44 deletions

View File

@ -51,15 +51,21 @@ func (g *Graph) Add(v dag.Vertex) dag.Vertex {
return v
}
// ConnectTo is a helper to create edges between a node and a list of
// targets by their DependableNames.
func (g *Graph) ConnectTo(source dag.Vertex, target []string) []string {
// ConnectDependent connects a GraphNodeDependent to all of its
// GraphNodeDependables. It returns the list of dependents it was
// unable to connect to.
func (g *Graph) ConnectDependent(raw dag.Vertex) []string {
g.once.Do(g.init)
v, ok := raw.(GraphNodeDependent)
if !ok {
return nil
}
var missing []string
for _, t := range target {
for _, t := range v.DependentOn() {
if dest := g.dependableMap[t]; dest != nil {
g.Connect(dag.BasicEdge(source, dest))
g.Connect(dag.BasicEdge(v, dest))
} else {
missing = append(missing, t)
}
@ -68,6 +74,20 @@ func (g *Graph) ConnectTo(source dag.Vertex, target []string) []string {
return missing
}
// ConnectDependents goes through the graph, connecting all the
// GraphNodeDependents to GraphNodeDependables. This is safe to call
// multiple times.
//
// To get details on whether dependencies could be found/made, the more
// specific ConnectDependent should be used.
func (g *Graph) ConnectDependents() {
for _, v := range g.Vertices() {
if dv, ok := v.(GraphNodeDependent); ok {
g.ConnectDependent(dv)
}
}
}
func (g *Graph) init() {
if g.Graph == nil {
g.Graph = new(dag.Graph)
@ -86,3 +106,11 @@ func (g *Graph) init() {
type GraphNodeDependable interface {
DependableName() []string
}
// GraphNodeDependent is an interface which says that a node depends
// on another GraphNodeDependable by some name. By implementing this
// interface, Graph.ConnectDependents() can be called multiple times
// safely and efficiently.
type GraphNodeDependent interface {
DependentOn() []string
}

View File

@ -14,10 +14,10 @@ import (
type graphNodeConfig interface {
dag.NamedVertex
// Variables returns the full list of variables that this node
// depends on. The values within the slice should map to the VarName()
// values that are returned by any nodes.
Variables() []string
// All graph nodes should be dependent on other things, and able to
// be depended on.
GraphNodeDependable
GraphNodeDependent
}
// GraphNodeConfigModule represents a module within the configuration graph.
@ -29,20 +29,23 @@ type GraphNodeConfigModule struct {
func (n *GraphNodeConfigModule) DependableName() []string {
return []string{n.Name()}
}
func (n *GraphNodeConfigModule) Name() string {
return fmt.Sprintf("module.%s", n.Module.Name)
}
func (n *GraphNodeConfigModule) Variables() []string {
func (n *GraphNodeConfigModule) DependentOn() []string {
vars := n.Module.RawConfig.Variables
result := make([]string, 0, len(vars))
for _, v := range vars {
result = append(result, varNameForVar(v))
if vn := varNameForVar(v); vn != "" {
result = append(result, vn)
}
}
return result
}
func (n *GraphNodeConfigModule) Name() string {
return fmt.Sprintf("module.%s", n.Module.Name)
}
// GraphNodeConfigProvider represents a configured provider within the
// configuration graph. These are only immediately in the graph when an
// explicit `provider` configuration block is in the configuration.
@ -54,11 +57,17 @@ func (n *GraphNodeConfigProvider) Name() string {
return fmt.Sprintf("provider.%s", n.Provider.Name)
}
func (n *GraphNodeConfigProvider) Variables() []string {
func (n *GraphNodeConfigProvider) DependableName() []string {
return []string{n.Name()}
}
func (n *GraphNodeConfigProvider) DependentOn() []string {
vars := n.Provider.RawConfig.Variables
result := make([]string, 0, len(vars))
for _, v := range vars {
result = append(result, varNameForVar(v))
if vn := varNameForVar(v); vn != "" {
result = append(result, vn)
}
}
return result
@ -73,22 +82,26 @@ func (n *GraphNodeConfigResource) DependableName() []string {
return []string{n.Resource.Id()}
}
func (n *GraphNodeConfigResource) Name() string {
return n.Resource.Id()
}
func (n *GraphNodeConfigResource) Variables() []string {
func (n *GraphNodeConfigResource) DependentOn() []string {
result := make([]string, len(n.Resource.DependsOn),
len(n.Resource.RawCount.Variables)+
len(n.Resource.RawConfig.Variables)+
len(n.Resource.DependsOn))
copy(result, n.Resource.DependsOn)
for _, v := range n.Resource.RawCount.Variables {
result = append(result, varNameForVar(v))
if vn := varNameForVar(v); vn != "" {
result = append(result, vn)
}
}
for _, v := range n.Resource.RawConfig.Variables {
result = append(result, varNameForVar(v))
if vn := varNameForVar(v); vn != "" {
result = append(result, vn)
}
}
return result
}
func (n *GraphNodeConfigResource) Name() string {
return n.Resource.Id()
}

View File

@ -18,12 +18,15 @@ func TestGraphAdd(t *testing.T) {
}
}
func TestGraphConnectTo(t *testing.T) {
func TestGraphConnectDependent(t *testing.T) {
var g Graph
g.Add(&testGraphDependable{VertexName: "a", Mock: []string{"a"}})
b := g.Add(&testGraphDependable{VertexName: "b"})
b := g.Add(&testGraphDependable{
VertexName: "b",
DependentOnMock: []string{"a"},
})
if missing := g.ConnectTo(b, []string{"a"}); len(missing) > 0 {
if missing := g.ConnectDependent(b); len(missing) > 0 {
t.Fatalf("bad: %#v", missing)
}
@ -35,8 +38,9 @@ func TestGraphConnectTo(t *testing.T) {
}
type testGraphDependable struct {
VertexName string
Mock []string
VertexName string
DependentOnMock []string
Mock []string
}
func (v *testGraphDependable) Name() string {
@ -47,6 +51,10 @@ func (v *testGraphDependable) DependableName() []string {
return v.Mock
}
func (v *testGraphDependable) DependentOn() []string {
return v.DependentOnMock
}
const testGraphAddStr = `
42
84

View File

@ -63,14 +63,7 @@ func (t *ConfigTransformer) Transform(g *Graph) error {
// Build up the dependencies. We have to do this outside of the above
// loop since the nodes need to be in place for us to build the deps.
for _, n := range nodes {
vars := n.Variables()
targets := make([]string, 0, len(vars))
for _, t := range vars {
if t != "" {
targets = append(targets, t)
}
}
if missing := g.ConnectTo(n, targets); len(missing) > 0 {
if missing := g.ConnectDependent(n); len(missing) > 0 {
for _, m := range missing {
err = multierror.Append(err, fmt.Errorf(
"%s: missing dependency: %s", n.Name(), m))

View File

@ -29,17 +29,21 @@ func (t *OrphanTransformer) Transform(g *Graph) error {
resourceOrphans := state.Orphans(t.Config)
resourceVertexes := make([]dag.Vertex, len(resourceOrphans))
for i, k := range resourceOrphans {
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{ResourceName: k})
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{
ResourceName: k,
dependentOn: state.Resources[k].Dependencies,
})
}
// Go over each module orphan and add it to the graph. We store the
// vertexes and states outside so that we can connect dependencies later.
moduleOrphans := t.State.ModuleOrphans(g.Path, t.Config)
moduleVertexes := make([]dag.Vertex, len(moduleOrphans))
moduleStates := make([]*ModuleState, len(moduleVertexes))
for i, path := range moduleOrphans {
moduleVertexes[i] = g.Add(&graphNodeOrphanModule{Path: path})
moduleStates[i] = t.State.ModuleByPath(path)
moduleVertexes[i] = g.Add(&graphNodeOrphanModule{
Path: path,
dependentOn: t.State.ModuleByPath(path).Dependencies,
})
}
// Now do the dependencies. We do this _after_ adding all the orphan
@ -47,13 +51,13 @@ func (t *OrphanTransformer) Transform(g *Graph) error {
// depend on other orphans.
// Resource dependencies
for i, v := range resourceVertexes {
g.ConnectTo(v, state.Resources[resourceOrphans[i]].Dependencies)
for _, v := range resourceVertexes {
g.ConnectDependent(v)
}
// Module dependencies
for i, v := range moduleVertexes {
g.ConnectTo(v, moduleStates[i].Dependencies)
for _, v := range moduleVertexes {
g.ConnectDependent(v)
}
return nil
@ -62,12 +66,18 @@ func (t *OrphanTransformer) Transform(g *Graph) error {
// graphNodeOrphanModule is the graph vertex representing an orphan resource..
type graphNodeOrphanModule struct {
Path []string
dependentOn []string
}
func (n *graphNodeOrphanModule) DependableName() []string {
return []string{n.dependableName()}
}
func (n *graphNodeOrphanModule) DependentOn() []string {
return n.dependentOn
}
func (n *graphNodeOrphanModule) Name() string {
return fmt.Sprintf("%s (orphan)", n.dependableName())
}
@ -79,12 +89,18 @@ func (n *graphNodeOrphanModule) dependableName() string {
// graphNodeOrphanResource is the graph vertex representing an orphan resource..
type graphNodeOrphanResource struct {
ResourceName string
dependentOn []string
}
func (n *graphNodeOrphanResource) DependableName() []string {
return []string{n.dependableName()}
}
func (n *graphNodeOrphanResource) DependentOn() []string {
return n.dependentOn
}
func (n *graphNodeOrphanResource) Name() string {
return fmt.Sprintf("%s (orphan)", n.ResourceName)
}