Merge pull request #10518 from hashicorp/b-graph
command/graph: work with new graphs
This commit is contained in:
commit
a4ceb4a772
|
@ -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)
|
||||
|
|
|
@ -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"),
|
||||
})
|
||||
|
||||
|
|
27
dag/dot.go
27
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
@ -47,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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,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{},
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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{},
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
@ -45,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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,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{},
|
||||
|
|
|
@ -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]]
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -4,8 +4,13 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"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 {
|
||||
|
@ -60,3 +65,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",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue