From 7b774f771bc972f7e6ec257496d753326f49c95c Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 9 Nov 2016 16:52:22 -0500 Subject: [PATCH] implement dag.Subgrapher interface This allows the dag package to detect subgraphs, even when impelemnted by types from other packages --- dag/dag.go | 4 ++++ dag/graph.go | 16 +++++++++++++++ dag/marshal.go | 23 ++++++++++------------ terraform/graph.go | 6 +++++- terraform/graph_config_node_module.go | 4 ++-- terraform/graph_config_node_module_test.go | 2 +- terraform/graph_debug.go | 4 ++-- terraform/graph_dot_test.go | 2 +- terraform/transform_expand.go | 4 ++-- terraform/transform_expand_test.go | 4 ++-- 10 files changed, 45 insertions(+), 24 deletions(-) diff --git a/dag/dag.go b/dag/dag.go index 1a76dc1cf..b58b40717 100644 --- a/dag/dag.go +++ b/dag/dag.go @@ -24,6 +24,10 @@ type WalkFunc func(Vertex) error // walk as an argument type DepthWalkFunc func(Vertex, int) error +func (g *AcyclicGraph) DirectedGraph() Grapher { + return g +} + // Returns a Set that includes every Vertex yielded by walking down from the // provided starting Vertex v. func (g *AcyclicGraph) Ancestors(v Vertex) (*Set, error) { diff --git a/dag/graph.go b/dag/graph.go index e713ff3ef..456eb0826 100644 --- a/dag/graph.go +++ b/dag/graph.go @@ -17,6 +17,18 @@ type Graph struct { once sync.Once } +// Subgrapher allows a Vertex to be a Graph itself, by returning a Grapher. +type Subgrapher interface { + Subgraph() Grapher +} + +// A Grapher is any type that returns a Grapher, mainly used to identify +// dag.Graph and dag.AcyclicGraph. In the case of Graph and AcyclicGraph, they +// return themselves. +type Grapher interface { + DirectedGraph() Grapher +} + // Vertex of the graph. type Vertex interface{} @@ -27,6 +39,10 @@ type NamedVertex interface { Name() string } +func (g *Graph) DirectedGraph() Grapher { + return g +} + // Vertices returns the list of all the vertices in the graph. func (g *Graph) Vertices() []Vertex { list := g.vertices.List() diff --git a/dag/marshal.go b/dag/marshal.go index 7b0f1e8e3..c44f0a4ce 100644 --- a/dag/marshal.go +++ b/dag/marshal.go @@ -60,7 +60,7 @@ func newMarshalGraph(name string, g *Graph) *marshalGraph { for _, v := range g.Vertices() { id := marshalVertexID(v) - if sg, ok := marshalSubgraph(v); ok { + if sg, ok := marshalSubgrapher(v); ok { sdg := newMarshalGraph(VertexName(v), sg) sdg.ID = id @@ -129,22 +129,19 @@ func marshalVertexID(v Vertex) string { panic("unhashable value in graph") } -func debugSubgraph(v Vertex) (*Graph, bool) { - val := reflect.ValueOf(v) - m, ok := val.Type().MethodByName("Subgraph") +// check for a Subgrapher, and return the underlying *Graph. +func marshalSubgrapher(v Vertex) (*Graph, bool) { + sg, ok := v.(Subgrapher) if !ok { return nil, false } - if m.Type.NumOut() != 1 { - return nil, false + switch g := sg.Subgraph().DirectedGraph().(type) { + case *Graph: + return g, true + case *AcyclicGraph: + return &g.Graph, true } - // can't check for the subgraph type, because we can't import terraform, so - // we assume this is the correct method. - // TODO: create a dag interface type that we can satisfy - - sg := val.MethodByName("Subgraph").Call(nil)[0] - ag := sg.Elem().FieldByName("AcyclicGraph").Interface().(AcyclicGraph) - return &ag.Graph, true + return nil, false } diff --git a/terraform/graph.go b/terraform/graph.go index 5f114a5c4..5174ef925 100644 --- a/terraform/graph.go +++ b/terraform/graph.go @@ -43,6 +43,10 @@ type Graph struct { once sync.Once } +func (g *Graph) DirectedGraph() dag.Grapher { + return &g.AcyclicGraph +} + // Annotations returns the annotations that are configured for the // given vertex. The map is guaranteed to be non-nil but may be empty. // @@ -317,7 +321,7 @@ func (g *Graph) walk(walker GraphWalker) error { walker.Debug().Printf( "[DEBUG] vertex %T(%s.%s): subgraph\n", v, path, dag.VertexName(v)) - if rerr = sn.Subgraph().walk(walker); rerr != nil { + if rerr = sn.Subgraph().(*Graph).walk(walker); rerr != nil { return } } diff --git a/terraform/graph_config_node_module.go b/terraform/graph_config_node_module.go index 4396c2e65..5ba36252b 100644 --- a/terraform/graph_config_node_module.go +++ b/terraform/graph_config_node_module.go @@ -156,7 +156,7 @@ func (n *graphNodeModuleExpanded) EvalTree() EvalNode { // GraphNodeFlattenable impl. func (n *graphNodeModuleExpanded) FlattenGraph() *Graph { - graph := n.Subgraph() + graph := n.Subgraph().(*Graph) input := n.Original.Module.RawConfig // Go over each vertex and do some modifications to the graph for @@ -189,7 +189,7 @@ func (n *graphNodeModuleExpanded) FlattenGraph() *Graph { } // GraphNodeSubgraph impl. -func (n *graphNodeModuleExpanded) Subgraph() *Graph { +func (n *graphNodeModuleExpanded) Subgraph() dag.Grapher { return n.Graph } diff --git a/terraform/graph_config_node_module_test.go b/terraform/graph_config_node_module_test.go index 6ba015a45..6aeacf788 100644 --- a/terraform/graph_config_node_module_test.go +++ b/terraform/graph_config_node_module_test.go @@ -33,7 +33,7 @@ func TestGraphNodeConfigModuleExpand(t *testing.T) { t.Fatalf("err: %s", err) } - actual := strings.TrimSpace(g.Subgraph().String()) + actual := strings.TrimSpace(g.Subgraph().(*Graph).String()) expected := strings.TrimSpace(testGraphNodeModuleExpandStr) if actual != expected { t.Fatalf("bad:\n\n%s", actual) diff --git a/terraform/graph_debug.go b/terraform/graph_debug.go index 80c84e780..181f5f4bf 100644 --- a/terraform/graph_debug.go +++ b/terraform/graph_debug.go @@ -184,7 +184,7 @@ func (dg *DebugGraph) buildSubgraph(modName string, g *Graph, modDepth int) erro for _, v := range g.Vertices() { if sn, ok := v.(GraphNodeSubgraph); ok { - subgraphVertices[v] = sn.Subgraph() + subgraphVertices[v] = sn.Subgraph().(*Graph) } } @@ -200,7 +200,7 @@ func (dg *DebugGraph) buildSubgraph(modName string, g *Graph, modDepth int) erro toDraw = append(toDraw, v) if sn, ok := v.(GraphNodeSubgraph); ok { - subgraphVertices[v] = sn.Subgraph() + subgraphVertices[v] = sn.Subgraph().(*Graph) } return nil } diff --git a/terraform/graph_dot_test.go b/terraform/graph_dot_test.go index cc4376e46..164327b8e 100644 --- a/terraform/graph_dot_test.go +++ b/terraform/graph_dot_test.go @@ -299,7 +299,7 @@ type testDrawableSubgraph struct { func (node *testDrawableSubgraph) Name() string { return node.VertexName } -func (node *testDrawableSubgraph) Subgraph() *Graph { +func (node *testDrawableSubgraph) Subgraph() dag.Grapher { return node.SubgraphMock } func (node *testDrawableSubgraph) DotNode(n string, opts *dag.DotOpts) *dot.Node { diff --git a/terraform/transform_expand.go b/terraform/transform_expand.go index e5120119a..b58c6bf17 100644 --- a/terraform/transform_expand.go +++ b/terraform/transform_expand.go @@ -24,7 +24,7 @@ type GraphNodeDynamicExpandable interface { // GraphNodeSubgraph is an interface a node can implement if it has // a larger subgraph that should be walked. type GraphNodeSubgraph interface { - Subgraph() *Graph + Subgraph() dag.Grapher } // ExpandTransform is a transformer that does a subgraph expansion @@ -56,7 +56,7 @@ func (n *GraphNodeBasicSubgraph) Name() string { return n.NameValue } -func (n *GraphNodeBasicSubgraph) Subgraph() *Graph { +func (n *GraphNodeBasicSubgraph) Subgraph() dag.Grapher { return n.Graph } diff --git a/terraform/transform_expand_test.go b/terraform/transform_expand_test.go index e0ee9cca2..d422eddf0 100644 --- a/terraform/transform_expand_test.go +++ b/terraform/transform_expand_test.go @@ -30,7 +30,7 @@ func TestExpandTransform(t *testing.T) { t.Fatalf("not subgraph: %#v", out) } - actual := strings.TrimSpace(sn.Subgraph().String()) + actual := strings.TrimSpace(sn.Subgraph().(*Graph).String()) expected := strings.TrimSpace(testExpandTransformStr) if actual != expected { t.Fatalf("bad: %s", actual) @@ -66,7 +66,7 @@ type testSubgraph struct { Graph *Graph } -func (n *testSubgraph) Subgraph() *Graph { +func (n *testSubgraph) Subgraph() dag.Grapher { return n.Graph }