github.com/pbthorste/terraform@v0.8.6-0.20170127005045-deb56bd93da2/terraform/graph_builder.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 8 "github.com/hashicorp/terraform/config/module" 9 ) 10 11 // GraphBuilder is an interface that can be implemented and used with 12 // Terraform to build the graph that Terraform walks. 13 type GraphBuilder interface { 14 // Build builds the graph for the given module path. It is up to 15 // the interface implementation whether this build should expand 16 // the graph or not. 17 Build(path []string) (*Graph, error) 18 } 19 20 // BasicGraphBuilder is a GraphBuilder that builds a graph out of a 21 // series of transforms and (optionally) validates the graph is a valid 22 // structure. 23 type BasicGraphBuilder struct { 24 Steps []GraphTransformer 25 Validate bool 26 // Optional name to add to the graph debug log 27 Name string 28 } 29 30 func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) { 31 g := &Graph{Path: path} 32 33 debugName := "graph.json" 34 if b.Name != "" { 35 debugName = b.Name + "-" + debugName 36 } 37 debugBuf := dbug.NewFileWriter(debugName) 38 g.SetDebugWriter(debugBuf) 39 defer debugBuf.Close() 40 41 for _, step := range b.Steps { 42 if step == nil { 43 continue 44 } 45 46 stepName := fmt.Sprintf("%T", step) 47 dot := strings.LastIndex(stepName, ".") 48 if dot >= 0 { 49 stepName = stepName[dot+1:] 50 } 51 52 debugOp := g.DebugOperation(stepName, "") 53 err := step.Transform(g) 54 55 errMsg := "" 56 if err != nil { 57 errMsg = err.Error() 58 } 59 debugOp.End(errMsg) 60 61 log.Printf( 62 "[TRACE] Graph after step %T:\n\n%s", 63 step, g.StringWithNodeTypes()) 64 65 if err != nil { 66 return g, err 67 } 68 } 69 70 // Validate the graph structure 71 if b.Validate { 72 if err := g.Validate(); err != nil { 73 log.Printf("[ERROR] Graph validation failed. Graph:\n\n%s", g.String()) 74 return nil, err 75 } 76 } 77 78 return g, nil 79 } 80 81 // BuiltinGraphBuilder is responsible for building the complete graph that 82 // Terraform uses for execution. It is an opinionated builder that defines 83 // the step order required to build a complete graph as is used and expected 84 // by Terraform. 85 // 86 // If you require a custom graph, you'll have to build it up manually 87 // on your own by building a new GraphBuilder implementation. 88 type BuiltinGraphBuilder struct { 89 // Root is the root module of the graph to build. 90 Root *module.Tree 91 92 // Diff is the diff. The proper module diffs will be looked up. 93 Diff *Diff 94 95 // State is the global state. The proper module states will be looked 96 // up by graph path. 97 State *State 98 99 // Providers is the list of providers supported. 100 Providers []string 101 102 // Provisioners is the list of provisioners supported. 103 Provisioners []string 104 105 // Targets is the user-specified list of resources to target. 106 Targets []string 107 108 // Destroy is set to true when we're in a `terraform destroy` or a 109 // `terraform plan -destroy` 110 Destroy bool 111 112 // Determines whether the GraphBuilder should perform graph validation before 113 // returning the Graph. Generally you want this to be done, except when you'd 114 // like to inspect a problematic graph. 115 Validate bool 116 117 // Verbose is set to true when the graph should be built "worst case", 118 // skipping any prune steps. This is used for early cycle detection during 119 // Validate and for manual inspection via `terraform graph -verbose`. 120 Verbose bool 121 } 122 123 // Build builds the graph according to the steps returned by Steps. 124 func (b *BuiltinGraphBuilder) Build(path []string) (*Graph, error) { 125 basic := &BasicGraphBuilder{ 126 Steps: b.Steps(path), 127 Validate: b.Validate, 128 Name: "BuiltinGraphBuilder", 129 } 130 131 return basic.Build(path) 132 } 133 134 // Steps returns the ordered list of GraphTransformers that must be executed 135 // to build a complete graph. 136 func (b *BuiltinGraphBuilder) Steps(path []string) []GraphTransformer { 137 steps := []GraphTransformer{ 138 // Create all our resources from the configuration and state 139 &ConfigTransformerOld{Module: b.Root}, 140 &OrphanTransformer{ 141 State: b.State, 142 Module: b.Root, 143 }, 144 145 // Output-related transformations 146 &AddOutputOrphanTransformer{State: b.State}, 147 148 // Provider-related transformations 149 &MissingProviderTransformer{Providers: b.Providers}, 150 &ProviderTransformer{}, 151 &DisableProviderTransformerOld{}, 152 153 // Provisioner-related transformations 154 &MissingProvisionerTransformer{Provisioners: b.Provisioners}, 155 &ProvisionerTransformer{}, 156 157 // Run our vertex-level transforms 158 &VertexTransformer{ 159 Transforms: []GraphVertexTransformer{ 160 // Expand any statically expanded nodes, such as module graphs 161 &ExpandTransform{ 162 Builder: b, 163 }, 164 }, 165 }, 166 167 // Flatten stuff 168 &FlattenTransformer{}, 169 170 // Make sure all the connections that are proxies are connected through 171 &ProxyTransformer{}, 172 } 173 174 // If we're on the root path, then we do a bunch of other stuff. 175 // We don't do the following for modules. 176 if len(path) <= 1 { 177 steps = append(steps, 178 // Optionally reduces the graph to a user-specified list of targets and 179 // their dependencies. 180 &TargetsTransformer{Targets: b.Targets, Destroy: b.Destroy}, 181 182 // Create orphan output nodes 183 &OrphanOutputTransformer{Module: b.Root, State: b.State}, 184 185 // Prune the providers. This must happen only once because flattened 186 // modules might depend on empty providers. 187 &PruneProviderTransformer{}, 188 189 // Create the destruction nodes 190 &DestroyTransformer{FullDestroy: b.Destroy}, 191 b.conditional(&conditionalOpts{ 192 If: func() bool { return !b.Destroy }, 193 Then: &CreateBeforeDestroyTransformer{}, 194 }), 195 b.conditional(&conditionalOpts{ 196 If: func() bool { return !b.Verbose }, 197 Then: &PruneDestroyTransformer{Diff: b.Diff, State: b.State}, 198 }), 199 200 // Remove the noop nodes 201 &PruneNoopTransformer{Diff: b.Diff, State: b.State}, 202 203 // Insert nodes to close opened plugin connections 204 &CloseProviderTransformer{}, 205 &CloseProvisionerTransformer{}, 206 207 // Perform the transitive reduction to make our graph a bit 208 // more sane if possible (it usually is possible). 209 &TransitiveReductionTransformer{}, 210 ) 211 } 212 213 // Make sure we have a single root 214 steps = append(steps, &RootTransformer{}) 215 216 // Remove nils 217 for i, s := range steps { 218 if s == nil { 219 steps = append(steps[:i], steps[i+1:]...) 220 } 221 } 222 223 return steps 224 } 225 226 type conditionalOpts struct { 227 If func() bool 228 Then GraphTransformer 229 } 230 231 func (b *BuiltinGraphBuilder) conditional(o *conditionalOpts) GraphTransformer { 232 if o.If != nil && o.Then != nil && o.If() { 233 return o.Then 234 } 235 return nil 236 }