From 8bf725e7468524d813f351ad87430e0b9613e1ee Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 27 Jan 2015 14:56:01 -0800 Subject: [PATCH] terraform: GraphNodeDependent --- terraform/graph.go | 38 ++++++++++++++++++++++---- terraform/graph_config_node.go | 49 +++++++++++++++++++++------------- terraform/graph_test.go | 18 +++++++++---- terraform/transform_config.go | 9 +------ terraform/transform_orphan.go | 32 ++++++++++++++++------ 5 files changed, 102 insertions(+), 44 deletions(-) diff --git a/terraform/graph.go b/terraform/graph.go index d9414db63d..4cb98b004d 100644 --- a/terraform/graph.go +++ b/terraform/graph.go @@ -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 +} diff --git a/terraform/graph_config_node.go b/terraform/graph_config_node.go index 1e52322140..28868b8035 100644 --- a/terraform/graph_config_node.go +++ b/terraform/graph_config_node.go @@ -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() +} diff --git a/terraform/graph_test.go b/terraform/graph_test.go index 7a70d231c6..c56f5bfade 100644 --- a/terraform/graph_test.go +++ b/terraform/graph_test.go @@ -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 diff --git a/terraform/transform_config.go b/terraform/transform_config.go index 5d87c7047b..0358f7b836 100644 --- a/terraform/transform_config.go +++ b/terraform/transform_config.go @@ -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)) diff --git a/terraform/transform_orphan.go b/terraform/transform_orphan.go index 418baa1be6..945d22df3c 100644 --- a/terraform/transform_orphan.go +++ b/terraform/transform_orphan.go @@ -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) }