From 91974228818a803afd2a60b4d94636e140461c7a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 2 Dec 2016 22:26:40 -0500 Subject: [PATCH 1/6] terraform: new graph nodes implement Dotter --- terraform/node_provider_abstract.go | 12 ++++++++++++ terraform/node_resource_abstract.go | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/terraform/node_provider_abstract.go b/terraform/node_provider_abstract.go index 5cb18caec..6d21bf51b 100644 --- a/terraform/node_provider_abstract.go +++ b/terraform/node_provider_abstract.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/dag" ) // NodeAbstractProvider represents a provider that has no associated operations. @@ -60,3 +61,14 @@ func (n *NodeAbstractProvider) ProviderConfig() *config.RawConfig { func (n *NodeAbstractProvider) AttachProvider(c *config.ProviderConfig) { n.Config = c } + +// GraphNodeDotter impl. +func (n *NodeAbstractProvider) DotNode(name string, opts *dag.DotOpts) *dag.DotNode { + return &dag.DotNode{ + Name: name, + Attrs: map[string]string{ + "label": n.Name(), + "shape": "diamond", + }, + } +} diff --git a/terraform/node_resource_abstract.go b/terraform/node_resource_abstract.go index f502547d7..22218e224 100644 --- a/terraform/node_resource_abstract.go +++ b/terraform/node_resource_abstract.go @@ -166,3 +166,14 @@ func (n *NodeAbstractResource) AttachResourceState(s *ResourceState) { func (n *NodeAbstractResource) AttachResourceConfig(c *config.Resource) { n.Config = c } + +// GraphNodeDotter impl. +func (n *NodeAbstractResource) DotNode(name string, opts *dag.DotOpts) *dag.DotNode { + return &dag.DotNode{ + Name: name, + Attrs: map[string]string{ + "label": n.Name(), + "shape": "box", + }, + } +} From fb8f2e2753886c4f2074a57984556855ca423db2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 2 Dec 2016 22:38:49 -0500 Subject: [PATCH 2/6] terraform: new Graph API that can return the graph for each op --- terraform/context.go | 92 +++++++++++++++---------- terraform/context_graph_type.go | 26 +++++++ terraform/graph_builder_apply.go | 5 +- terraform/graph_builder_destroy_plan.go | 5 +- terraform/graph_builder_plan.go | 5 +- terraform/graphtype_string.go | 16 +++++ 6 files changed, 111 insertions(+), 38 deletions(-) create mode 100644 terraform/context_graph_type.go create mode 100644 terraform/graphtype_string.go diff --git a/terraform/context.go b/terraform/context.go index fdd65cf6e..2ebeed097 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -189,13 +189,55 @@ func NewContext(opts *ContextOpts) (*Context, error) { } type ContextGraphOpts struct { + // If true, validates the graph structure (checks for cycles). Validate bool - Verbose bool + + // Legacy graphs only: won't prune the graph + Verbose bool } -// Graph returns the graph for this config. -func (c *Context) Graph(g *ContextGraphOpts) (*Graph, error) { - return c.graphBuilder(g).Build(RootModulePath) +// Graph returns the graph used for the given operation type. +// +// The most extensive or complex graph type is GraphTypePlan. +func (c *Context) Graph(typ GraphType, opts *ContextGraphOpts) (*Graph, error) { + if opts == nil { + opts = &ContextGraphOpts{Validate: true} + } + + switch typ { + case GraphTypeApply: + return (&ApplyGraphBuilder{ + Module: c.module, + Diff: c.diff, + State: c.state, + Providers: c.components.ResourceProviders(), + Provisioners: c.components.ResourceProvisioners(), + Destroy: c.destroy, + Validate: opts.Validate, + }).Build(RootModulePath) + + case GraphTypePlan: + return (&PlanGraphBuilder{ + Module: c.module, + State: c.state, + Providers: c.components.ResourceProviders(), + Targets: c.targets, + Validate: opts.Validate, + }).Build(RootModulePath) + + case GraphTypePlanDestroy: + return (&DestroyPlanGraphBuilder{ + Module: c.module, + State: c.state, + Targets: c.targets, + Validate: opts.Validate, + }).Build(RootModulePath) + + case GraphTypeLegacy: + return c.graphBuilder(opts).Build(RootModulePath) + } + + return nil, fmt.Errorf("unknown graph type: %s", typ) } // GraphBuilder returns the GraphBuilder that will be used to create @@ -360,7 +402,7 @@ func (c *Context) Input(mode InputMode) error { if mode&InputModeProvider != 0 { // Build the graph - graph, err := c.Graph(&ContextGraphOpts{Validate: true}) + graph, err := c.Graph(GraphTypeLegacy, nil) if err != nil { return err } @@ -390,20 +432,11 @@ func (c *Context) Apply() (*State, error) { X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph) // Build the graph. - var graph *Graph - var err error + graphType := GraphTypeLegacy if !X_legacyGraph { - graph, err = (&ApplyGraphBuilder{ - Module: c.module, - Diff: c.diff, - State: c.state, - Providers: c.components.ResourceProviders(), - Provisioners: c.components.ResourceProvisioners(), - Destroy: c.destroy, - }).Build(RootModulePath) - } else { - graph, err = c.Graph(&ContextGraphOpts{Validate: true}) + graphType = GraphTypeApply } + graph, err := c.Graph(graphType, nil) if err != nil { return nil, err } @@ -475,26 +508,15 @@ func (c *Context) Plan() (*Plan, error) { X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph) // Build the graph. - var graph *Graph - var err error + graphType := GraphTypeLegacy if !X_legacyGraph { if c.destroy { - graph, err = (&DestroyPlanGraphBuilder{ - Module: c.module, - State: c.state, - Targets: c.targets, - }).Build(RootModulePath) + graphType = GraphTypePlanDestroy } else { - graph, err = (&PlanGraphBuilder{ - Module: c.module, - State: c.state, - Providers: c.components.ResourceProviders(), - Targets: c.targets, - }).Build(RootModulePath) + graphType = GraphTypePlan } - } else { - graph, err = c.Graph(&ContextGraphOpts{Validate: true}) } + graph, err := c.Graph(graphType, nil) if err != nil { return nil, err } @@ -522,7 +544,7 @@ func (c *Context) Plan() (*Plan, error) { if X_legacyGraph { // Now that we have a diff, we can build the exact graph that Apply will use // and catch any possible cycles during the Plan phase. - if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil { + if _, err := c.Graph(GraphTypeLegacy, nil); err != nil { return nil, err } } @@ -548,7 +570,7 @@ func (c *Context) Refresh() (*State, error) { c.state = c.state.DeepCopy() // Build the graph - graph, err := c.Graph(&ContextGraphOpts{Validate: true}) + graph, err := c.Graph(GraphTypeLegacy, nil) if err != nil { return nil, err } @@ -619,7 +641,7 @@ func (c *Context) Validate() ([]string, []error) { // We also validate the graph generated here, but this graph doesn't // necessarily match the graph that Plan will generate, so we'll validate the // graph again later after Planning. - graph, err := c.Graph(&ContextGraphOpts{Validate: true}) + graph, err := c.Graph(GraphTypeLegacy, nil) if err != nil { return nil, []error{err} } diff --git a/terraform/context_graph_type.go b/terraform/context_graph_type.go new file mode 100644 index 000000000..a204969ff --- /dev/null +++ b/terraform/context_graph_type.go @@ -0,0 +1,26 @@ +package terraform + +//go:generate stringer -type=GraphType context_graph_type.go + +// GraphType is an enum of the type of graph to create with a Context. +// The values of the constants may change so they shouldn't be depended on; +// always use the constant name. +type GraphType byte + +const ( + GraphTypeInvalid GraphType = 0 + GraphTypeLegacy GraphType = iota + GraphTypePlan + GraphTypePlanDestroy + GraphTypeApply +) + +// GraphTypeMap is a mapping of human-readable string to GraphType. This +// is useful to use as the mechanism for human input for configurable +// graph types. +var GraphTypeMap = map[string]GraphType{ + "apply": GraphTypeApply, + "plan": GraphTypePlan, + "plan-destroy": GraphTypePlanDestroy, + "legacy": GraphTypeLegacy, +} diff --git a/terraform/graph_builder_apply.go b/terraform/graph_builder_apply.go index 1f8794c25..6837c6769 100644 --- a/terraform/graph_builder_apply.go +++ b/terraform/graph_builder_apply.go @@ -33,13 +33,16 @@ type ApplyGraphBuilder struct { // Destroy, if true, represents a pure destroy operation Destroy bool + + // Validate will do structural validation of the graph. + Validate bool } // See GraphBuilder func (b *ApplyGraphBuilder) Build(path []string) (*Graph, error) { return (&BasicGraphBuilder{ Steps: b.Steps(), - Validate: true, + Validate: b.Validate, Name: "ApplyGraphBuilder", }).Build(path) } diff --git a/terraform/graph_builder_destroy_plan.go b/terraform/graph_builder_destroy_plan.go index 5a85f9a63..014b348e5 100644 --- a/terraform/graph_builder_destroy_plan.go +++ b/terraform/graph_builder_destroy_plan.go @@ -19,13 +19,16 @@ type DestroyPlanGraphBuilder struct { // Targets are resources to target Targets []string + + // Validate will do structural validation of the graph. + Validate bool } // See GraphBuilder func (b *DestroyPlanGraphBuilder) Build(path []string) (*Graph, error) { return (&BasicGraphBuilder{ Steps: b.Steps(), - Validate: true, + Validate: b.Validate, Name: "DestroyPlanGraphBuilder", }).Build(path) } diff --git a/terraform/graph_builder_plan.go b/terraform/graph_builder_plan.go index b7d6c8aa1..85259e702 100644 --- a/terraform/graph_builder_plan.go +++ b/terraform/graph_builder_plan.go @@ -31,13 +31,16 @@ type PlanGraphBuilder struct { // DisableReduce, if true, will not reduce the graph. Great for testing. DisableReduce bool + + // Validate will do structural validation of the graph. + Validate bool } // See GraphBuilder func (b *PlanGraphBuilder) Build(path []string) (*Graph, error) { return (&BasicGraphBuilder{ Steps: b.Steps(), - Validate: true, + Validate: b.Validate, Name: "PlanGraphBuilder", }).Build(path) } diff --git a/terraform/graphtype_string.go b/terraform/graphtype_string.go new file mode 100644 index 000000000..8e644abb9 --- /dev/null +++ b/terraform/graphtype_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=GraphType context_graph_type.go"; DO NOT EDIT + +package terraform + +import "fmt" + +const _GraphType_name = "GraphTypeInvalidGraphTypeLegacyGraphTypePlanGraphTypePlanDestroyGraphTypeApply" + +var _GraphType_index = [...]uint8{0, 16, 31, 44, 64, 78} + +func (i GraphType) String() string { + if i >= GraphType(len(_GraphType_index)-1) { + return fmt.Sprintf("GraphType(%d)", i) + } + return _GraphType_name[_GraphType_index[i]:_GraphType_index[i+1]] +} From 22e868b966aba83031e93abf31c2e5b27ee9488b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 3 Dec 2016 15:00:34 -0800 Subject: [PATCH 3/6] command/graph: update for new graphs --- command/graph.go | 43 ++++++++++++++++++++++++++++++++----------- command/graph_test.go | 13 +++++++++++++ 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/command/graph.go b/command/graph.go index ac59429a4..b127c54b9 100644 --- a/command/graph.go +++ b/command/graph.go @@ -20,6 +20,7 @@ func (c *GraphCommand) Run(args []string) int { var moduleDepth int var verbose bool var drawCycles bool + var graphTypeStr string args = c.Meta.process(args, false) @@ -27,6 +28,7 @@ func (c *GraphCommand) Run(args []string) int { c.addModuleDepthFlag(cmdFlags, &moduleDepth) cmdFlags.BoolVar(&verbose, "verbose", false, "verbose") cmdFlags.BoolVar(&drawCycles, "draw-cycles", false, "draw-cycles") + cmdFlags.StringVar(&graphTypeStr, "type", "", "type") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 @@ -48,7 +50,7 @@ func (c *GraphCommand) Run(args []string) int { } } - ctx, _, err := c.Context(contextOpts{ + ctx, planFile, err := c.Context(contextOpts{ Path: path, StatePath: "", }) @@ -57,9 +59,25 @@ func (c *GraphCommand) Run(args []string) int { return 1 } + // Determine the graph type + graphType := terraform.GraphTypePlan + if planFile { + graphType = terraform.GraphTypeApply + } + + if graphTypeStr != "" { + v, ok := terraform.GraphTypeMap[graphTypeStr] + if !ok { + c.Ui.Error(fmt.Sprintf("Invalid graph type requested: %s", graphTypeStr)) + return 1 + } + + graphType = v + } + // Skip validation during graph generation - we want to see the graph even if // it is invalid for some reason. - g, err := ctx.Graph(&terraform.ContextGraphOpts{ + g, err := ctx.Graph(graphType, &terraform.ContextGraphOpts{ Verbose: verbose, Validate: false, }) @@ -87,25 +105,28 @@ func (c *GraphCommand) Help() string { helpText := ` Usage: terraform graph [options] [DIR] - Outputs the visual dependency graph of Terraform resources according to + Outputs the visual execution graph of Terraform resources according to configuration files in DIR (or the current directory if omitted). The graph is outputted in DOT format. The typical program that can read this format is GraphViz, but many web services are also available to read this format. + The -type flag can be used to control the type of graph shown. Terraform + creates different graphs for different operations. See the options below + for the list of types supported. The default type is "plan" if a + configuration is given, and "apply" if a plan file is passed as an + argument. + Options: - -draw-cycles Highlight any cycles in the graph with colored edges. - This helps when diagnosing cycle errors. + -draw-cycles Highlight any cycles in the graph with colored edges. + This helps when diagnosing cycle errors. - -module-depth=n The maximum depth to expand modules. By default this is - -1, which will expand resources within all modules. + -no-color If specified, output won't contain any color. - -verbose Generate a verbose, "worst-case" graph, with all nodes - for potential operations in place. - - -no-color If specified, output won't contain any color. + -type=plan Type of graph to output. Can be: plan, plan-destroy, apply, + legacy. ` return strings.TrimSpace(helpText) diff --git a/command/graph_test.go b/command/graph_test.go index 0139142c0..716218a7a 100644 --- a/command/graph_test.go +++ b/command/graph_test.go @@ -80,6 +80,19 @@ func TestGraph_noArgs(t *testing.T) { func TestGraph_plan(t *testing.T) { planPath := testPlanFile(t, &terraform.Plan{ + Diff: &terraform.Diff{ + Modules: []*terraform.ModuleDiff{ + &terraform.ModuleDiff{ + Path: []string{"root"}, + Resources: map[string]*terraform.InstanceDiff{ + "test_instance.bar": &terraform.InstanceDiff{ + Destroy: true, + }, + }, + }, + }, + }, + Module: testModule(t, "graph"), }) From 8a9e1c152757b7573a677898ff528bde68e17373 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 3 Dec 2016 15:10:26 -0800 Subject: [PATCH 4/6] dag: call into DotNode to get attributes --- dag/dot.go | 27 ++++++++++++++++++++++++--- dag/marshal.go | 24 ++++++++---------------- dag/marshal_test.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 19 deletions(-) diff --git a/dag/dot.go b/dag/dot.go index fc7754c12..2deb7ccd3 100644 --- a/dag/dot.go +++ b/dag/dot.go @@ -85,8 +85,29 @@ func (v *marshalVertex) dot(g *marshalGraph) []byte { if graphName == "" { graphName = "root" } - buf.WriteString(fmt.Sprintf(`"[%s] %s"`, graphName, v.Name)) - writeAttrs(&buf, v.Attrs) + + name := v.Name + attrs := v.Attrs + if v.graphNodeDotter != nil { + node := v.graphNodeDotter.DotNode(name, nil) + if node == nil { + return []byte{} + } + + newAttrs := make(map[string]string) + for k, v := range attrs { + newAttrs[k] = v + } + for k, v := range node.Attrs { + newAttrs[k] = v + } + + name = node.Name + attrs = newAttrs + } + + buf.WriteString(fmt.Sprintf(`"[%s] %s"`, graphName, name)) + writeAttrs(&buf, attrs) buf.WriteByte('\n') return buf.Bytes() @@ -145,7 +166,7 @@ func (g *marshalGraph) writeBody(opts *DotOpts, w *indentWriter) { skip := map[string]bool{} for _, v := range g.Vertices { - if !v.graphNodeDotter { + if v.graphNodeDotter == nil { skip[v.ID] = true continue } diff --git a/dag/marshal.go b/dag/marshal.go index 77bdc6509..16d5dd6dd 100644 --- a/dag/marshal.go +++ b/dag/marshal.go @@ -102,32 +102,24 @@ type marshalVertex struct { Attrs map[string]string `json:",omitempty"` // This is to help transition from the old Dot interfaces. We record if the - // node was a GraphNodeDotter here, so we know if it should be included in the - // dot output - graphNodeDotter bool + // node was a GraphNodeDotter here, so we can call it to get attributes. + graphNodeDotter GraphNodeDotter } func newMarshalVertex(v Vertex) *marshalVertex { + dn, ok := v.(GraphNodeDotter) + if !ok { + dn = nil + } + return &marshalVertex{ ID: marshalVertexID(v), Name: VertexName(v), Attrs: make(map[string]string), - graphNodeDotter: isDotter(v), + graphNodeDotter: dn, } } -func isDotter(v Vertex) bool { - dn, isDotter := v.(GraphNodeDotter) - dotOpts := &DotOpts{ - Verbose: true, - DrawCycles: true, - } - if isDotter && dn.DotNode("fake", dotOpts) == nil { - isDotter = false - } - return isDotter -} - // vertices is a sort.Interface implementation for sorting vertices by ID type vertices []*marshalVertex diff --git a/dag/marshal_test.go b/dag/marshal_test.go index 14daf6424..9201e0208 100644 --- a/dag/marshal_test.go +++ b/dag/marshal_test.go @@ -34,6 +34,27 @@ func TestGraphDot_basic(t *testing.T) { } } +func TestGraphDot_attrs(t *testing.T) { + var g Graph + g.Add(&testGraphNodeDotter{ + Result: &DotNode{ + Name: "foo", + Attrs: map[string]string{"foo": "bar"}, + }, + }) + + actual := strings.TrimSpace(string(g.Dot(nil))) + expected := strings.TrimSpace(testGraphDotAttrsStr) + if actual != expected { + t.Fatalf("bad: %s", actual) + } +} + +type testGraphNodeDotter struct{ Result *DotNode } + +func (n *testGraphNodeDotter) Name() string { return n.Result.Name } +func (n *testGraphNodeDotter) DotNode(string, *DotOpts) *DotNode { return n.Result } + const testGraphDotBasicStr = `digraph { compound = "true" newrank = "true" @@ -50,6 +71,14 @@ const testGraphDotEmptyStr = `digraph { } }` +const testGraphDotAttrsStr = `digraph { + compound = "true" + newrank = "true" + subgraph "root" { + "[root] foo" [foo = "bar"] + } +}` + func TestGraphJSON_empty(t *testing.T) { var g Graph g.Add(1) From 26ac58bc979c9590e602fd792562635655a4ce10 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 3 Dec 2016 15:27:38 -0800 Subject: [PATCH 5/6] terraform: refactor NodeApplyableProvider to use NodeAbstractProvider This is important so that the graph looks correct. --- terraform/graph_builder_apply.go | 7 ++-- terraform/graph_builder_import.go | 8 ++--- terraform/graph_builder_plan.go | 7 ++-- terraform/node_provider.go | 55 +---------------------------- terraform/node_provider_abstract.go | 4 +++ terraform/transform_provider.go | 15 ++++---- 6 files changed, 24 insertions(+), 72 deletions(-) diff --git a/terraform/graph_builder_apply.go b/terraform/graph_builder_apply.go index 6837c6769..e268204c4 100644 --- a/terraform/graph_builder_apply.go +++ b/terraform/graph_builder_apply.go @@ -50,10 +50,9 @@ func (b *ApplyGraphBuilder) Build(path []string) (*Graph, error) { // See GraphBuilder func (b *ApplyGraphBuilder) Steps() []GraphTransformer { // Custom factory for creating providers. - providerFactory := func(name string, path []string) GraphNodeProvider { + concreteProvider := func(a *NodeAbstractProvider) dag.Vertex { return &NodeApplyableProvider{ - NameValue: name, - PathValue: path, + NodeAbstractProvider: a, } } @@ -90,7 +89,7 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { ), // Create all the providers - &MissingProviderTransformer{Providers: b.Providers, Factory: providerFactory}, + &MissingProviderTransformer{Providers: b.Providers, Concrete: concreteProvider}, &ProviderTransformer{}, &DisableProviderTransformer{}, &ParentProviderTransformer{}, diff --git a/terraform/graph_builder_import.go b/terraform/graph_builder_import.go index 2a877515d..46bd97746 100644 --- a/terraform/graph_builder_import.go +++ b/terraform/graph_builder_import.go @@ -2,6 +2,7 @@ package terraform import ( "github.com/hashicorp/terraform/config/module" + "github.com/hashicorp/terraform/dag" ) // ImportGraphBuilder implements GraphBuilder and is responsible for building @@ -38,10 +39,9 @@ func (b *ImportGraphBuilder) Steps() []GraphTransformer { } // Custom factory for creating providers. - providerFactory := func(name string, path []string) GraphNodeProvider { + concreteProvider := func(a *NodeAbstractProvider) dag.Vertex { return &NodeApplyableProvider{ - NameValue: name, - PathValue: path, + NodeAbstractProvider: a, } } @@ -53,7 +53,7 @@ func (b *ImportGraphBuilder) Steps() []GraphTransformer { &ImportStateTransformer{Targets: b.ImportTargets}, // Provider-related transformations - &MissingProviderTransformer{Providers: b.Providers, Factory: providerFactory}, + &MissingProviderTransformer{Providers: b.Providers, Concrete: concreteProvider}, &ProviderTransformer{}, &DisableProviderTransformerOld{}, &PruneProviderTransformer{}, diff --git a/terraform/graph_builder_plan.go b/terraform/graph_builder_plan.go index 85259e702..7dd5708d3 100644 --- a/terraform/graph_builder_plan.go +++ b/terraform/graph_builder_plan.go @@ -48,10 +48,9 @@ func (b *PlanGraphBuilder) Build(path []string) (*Graph, error) { // See GraphBuilder func (b *PlanGraphBuilder) Steps() []GraphTransformer { // Custom factory for creating providers. - providerFactory := func(name string, path []string) GraphNodeProvider { + concreteProvider := func(a *NodeAbstractProvider) dag.Vertex { return &NodeApplyableProvider{ - NameValue: name, - PathValue: path, + NodeAbstractProvider: a, } } @@ -98,7 +97,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { &TargetsTransformer{Targets: b.Targets}, // Create all the providers - &MissingProviderTransformer{Providers: b.Providers, Factory: providerFactory}, + &MissingProviderTransformer{Providers: b.Providers, Concrete: concreteProvider}, &ProviderTransformer{}, &DisableProviderTransformer{}, &ParentProviderTransformer{}, diff --git a/terraform/node_provider.go b/terraform/node_provider.go index 9b8c34b4d..8e2c176fa 100644 --- a/terraform/node_provider.go +++ b/terraform/node_provider.go @@ -1,61 +1,8 @@ package terraform -import ( - "fmt" - - "github.com/hashicorp/terraform/config" -) - // NodeApplyableProvider represents a provider during an apply. -// -// NOTE: There is a lot of logic here that will be shared with non-Apply. -// The plan is to abstract that eventually into an embedded abstract struct. type NodeApplyableProvider struct { - NameValue string - PathValue []string - Config *config.ProviderConfig -} - -func (n *NodeApplyableProvider) Name() string { - result := fmt.Sprintf("provider.%s", n.NameValue) - if len(n.PathValue) > 1 { - result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result) - } - - return result -} - -// GraphNodeSubPath -func (n *NodeApplyableProvider) Path() []string { - return n.PathValue -} - -// GraphNodeReferencer -func (n *NodeApplyableProvider) References() []string { - if n.Config == nil { - return nil - } - - return ReferencesFromConfig(n.Config.RawConfig) -} - -// GraphNodeProvider -func (n *NodeApplyableProvider) ProviderName() string { - return n.NameValue -} - -// GraphNodeProvider -func (n *NodeApplyableProvider) ProviderConfig() *config.RawConfig { - if n.Config == nil { - return nil - } - - return n.Config.RawConfig -} - -// GraphNodeAttachProvider -func (n *NodeApplyableProvider) AttachProvider(c *config.ProviderConfig) { - n.Config = c + *NodeAbstractProvider } // GraphNodeEvalable diff --git a/terraform/node_provider_abstract.go b/terraform/node_provider_abstract.go index 6d21bf51b..f82c3f398 100644 --- a/terraform/node_provider_abstract.go +++ b/terraform/node_provider_abstract.go @@ -7,6 +7,10 @@ import ( "github.com/hashicorp/terraform/dag" ) +// ConcreteProviderNodeFunc is a callback type used to convert an +// abstract provider to a concrete one of some type. +type ConcreteProviderNodeFunc func(*NodeAbstractProvider) dag.Vertex + // NodeAbstractProvider represents a provider that has no associated operations. // It registers all the common interfaces across operations for providers. type NodeAbstractProvider struct { diff --git a/terraform/transform_provider.go b/terraform/transform_provider.go index c995186dc..e4eba60c4 100644 --- a/terraform/transform_provider.go +++ b/terraform/transform_provider.go @@ -114,15 +114,15 @@ type MissingProviderTransformer struct { // Providers is the list of providers we support. Providers []string - // Factory, if set, overrides how the providers are made. - Factory func(name string, path []string) GraphNodeProvider + // Concrete, if set, overrides how the providers are made. + Concrete ConcreteProviderNodeFunc } func (t *MissingProviderTransformer) Transform(g *Graph) error { // Initialize factory - if t.Factory == nil { - t.Factory = func(name string, path []string) GraphNodeProvider { - return &graphNodeProvider{ProviderNameValue: name} + if t.Concrete == nil { + t.Concrete = func(a *NodeAbstractProvider) dag.Vertex { + return &graphNodeProvider{ProviderNameValue: a.NameValue} } } @@ -177,7 +177,10 @@ func (t *MissingProviderTransformer) Transform(g *Graph) error { } // Add the missing provider node to the graph - v := t.Factory(p, path).(dag.Vertex) + v := t.Concrete(&NodeAbstractProvider{ + NameValue: p, + PathValue: path, + }).(dag.Vertex) if len(path) > 0 { if fn, ok := v.(GraphNodeFlattenable); ok { var err error From 5b444842a0905d254bec552f1acd4b7290b7e21c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 3 Dec 2016 15:29:04 -0800 Subject: [PATCH 6/6] website: update graph command --- website/source/docs/commands/graph.html.markdown | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/website/source/docs/commands/graph.html.markdown b/website/source/docs/commands/graph.html.markdown index 611738ec0..3098c5139 100644 --- a/website/source/docs/commands/graph.html.markdown +++ b/website/source/docs/commands/graph.html.markdown @@ -25,16 +25,20 @@ The graph is outputted in DOT format. The typical program that can read this format is GraphViz, but many web services are also available to read this format. +The -type flag can be used to control the type of graph shown. Terraform +creates different graphs for different operations. See the options below +for the list of types supported. The default type is "plan" if a +configuration is given, and "apply" if a plan file is passed as an +argument. + Options: * `-draw-cycles` - Highlight any cycles in the graph with colored edges. This helps when diagnosing cycle errors. -* `-module-depth=n` - The maximum depth to expand modules. By default this is - -1, which will expand all modules. +* `-no-color` - If specified, output won't contain any color. -* `-verbose` - Generate a verbose, "worst-case" graph, with all nodes - for potential operations in place. +* `-type=plan` - Type of graph to output. Can be: plan, plan-destroy, apply, legacy. ## Generating Images