github.com/rhenning/terraform@v0.8.0-beta2/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 := "build-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 // always log the graph state to see what transformations may have happened 62 debugName := "build-" + stepName 63 if b.Name != "" { 64 debugName = b.Name + "-" + debugName 65 } 66 67 log.Printf( 68 "[TRACE] Graph after step %T:\n\n%s", 69 step, g.StringWithNodeTypes()) 70 71 // TODO: replace entirely with the json logs 72 dbug.WriteGraph(debugName, g) 73 74 if err != nil { 75 return g, err 76 } 77 } 78 79 // Validate the graph structure 80 if b.Validate { 81 if err := g.Validate(); err != nil { 82 log.Printf("[ERROR] Graph validation failed. Graph:\n\n%s", g.String()) 83 return nil, err 84 } 85 } 86 87 return g, nil 88 } 89 90 // BuiltinGraphBuilder is responsible for building the complete graph that 91 // Terraform uses for execution. It is an opinionated builder that defines 92 // the step order required to build a complete graph as is used and expected 93 // by Terraform. 94 // 95 // If you require a custom graph, you'll have to build it up manually 96 // on your own by building a new GraphBuilder implementation. 97 type BuiltinGraphBuilder struct { 98 // Root is the root module of the graph to build. 99 Root *module.Tree 100 101 // Diff is the diff. The proper module diffs will be looked up. 102 Diff *Diff 103 104 // State is the global state. The proper module states will be looked 105 // up by graph path. 106 State *State 107 108 // Providers is the list of providers supported. 109 Providers []string 110 111 // Provisioners is the list of provisioners supported. 112 Provisioners []string 113 114 // Targets is the user-specified list of resources to target. 115 Targets []string 116 117 // Destroy is set to true when we're in a `terraform destroy` or a 118 // `terraform plan -destroy` 119 Destroy bool 120 121 // Determines whether the GraphBuilder should perform graph validation before 122 // returning the Graph. Generally you want this to be done, except when you'd 123 // like to inspect a problematic graph. 124 Validate bool 125 126 // Verbose is set to true when the graph should be built "worst case", 127 // skipping any prune steps. This is used for early cycle detection during 128 // Validate and for manual inspection via `terraform graph -verbose`. 129 Verbose bool 130 } 131 132 // Build builds the graph according to the steps returned by Steps. 133 func (b *BuiltinGraphBuilder) Build(path []string) (*Graph, error) { 134 basic := &BasicGraphBuilder{ 135 Steps: b.Steps(path), 136 Validate: b.Validate, 137 Name: "builtin", 138 } 139 140 return basic.Build(path) 141 } 142 143 // Steps returns the ordered list of GraphTransformers that must be executed 144 // to build a complete graph. 145 func (b *BuiltinGraphBuilder) Steps(path []string) []GraphTransformer { 146 steps := []GraphTransformer{ 147 // Create all our resources from the configuration and state 148 &ConfigTransformerOld{Module: b.Root}, 149 &OrphanTransformer{ 150 State: b.State, 151 Module: b.Root, 152 }, 153 154 // Output-related transformations 155 &AddOutputOrphanTransformer{State: b.State}, 156 157 // Provider-related transformations 158 &MissingProviderTransformer{Providers: b.Providers}, 159 &ProviderTransformer{}, 160 &DisableProviderTransformerOld{}, 161 162 // Provisioner-related transformations 163 &MissingProvisionerTransformer{Provisioners: b.Provisioners}, 164 &ProvisionerTransformer{}, 165 166 // Run our vertex-level transforms 167 &VertexTransformer{ 168 Transforms: []GraphVertexTransformer{ 169 // Expand any statically expanded nodes, such as module graphs 170 &ExpandTransform{ 171 Builder: b, 172 }, 173 }, 174 }, 175 176 // Flatten stuff 177 &FlattenTransformer{}, 178 179 // Make sure all the connections that are proxies are connected through 180 &ProxyTransformer{}, 181 } 182 183 // If we're on the root path, then we do a bunch of other stuff. 184 // We don't do the following for modules. 185 if len(path) <= 1 { 186 steps = append(steps, 187 // Optionally reduces the graph to a user-specified list of targets and 188 // their dependencies. 189 &TargetsTransformer{Targets: b.Targets, Destroy: b.Destroy}, 190 191 // Create orphan output nodes 192 &OrphanOutputTransformer{Module: b.Root, State: b.State}, 193 194 // Prune the providers. This must happen only once because flattened 195 // modules might depend on empty providers. 196 &PruneProviderTransformer{}, 197 198 // Create the destruction nodes 199 &DestroyTransformer{FullDestroy: b.Destroy}, 200 b.conditional(&conditionalOpts{ 201 If: func() bool { return !b.Destroy }, 202 Then: &CreateBeforeDestroyTransformer{}, 203 }), 204 b.conditional(&conditionalOpts{ 205 If: func() bool { return !b.Verbose }, 206 Then: &PruneDestroyTransformer{Diff: b.Diff, State: b.State}, 207 }), 208 209 // Remove the noop nodes 210 &PruneNoopTransformer{Diff: b.Diff, State: b.State}, 211 212 // Insert nodes to close opened plugin connections 213 &CloseProviderTransformer{}, 214 &CloseProvisionerTransformer{}, 215 216 // Perform the transitive reduction to make our graph a bit 217 // more sane if possible (it usually is possible). 218 &TransitiveReductionTransformer{}, 219 ) 220 } 221 222 // Make sure we have a single root 223 steps = append(steps, &RootTransformer{}) 224 225 // Remove nils 226 for i, s := range steps { 227 if s == nil { 228 steps = append(steps[:i], steps[i+1:]...) 229 } 230 } 231 232 return steps 233 } 234 235 type conditionalOpts struct { 236 If func() bool 237 Then GraphTransformer 238 } 239 240 func (b *BuiltinGraphBuilder) conditional(o *conditionalOpts) GraphTransformer { 241 if o.If != nil && o.Then != nil && o.If() { 242 return o.Then 243 } 244 return nil 245 }