github.com/grange74/terraform@v0.7.0-rc3.0.20160722171430-8c8803864753/config/module/tree.go (about) 1 package module 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "path/filepath" 8 "strings" 9 "sync" 10 11 "github.com/hashicorp/go-getter" 12 "github.com/hashicorp/terraform/config" 13 ) 14 15 // RootName is the name of the root tree. 16 const RootName = "root" 17 18 // Tree represents the module import tree of configurations. 19 // 20 // This Tree structure can be used to get (download) new modules, load 21 // all the modules without getting, flatten the tree into something 22 // Terraform can use, etc. 23 type Tree struct { 24 name string 25 config *config.Config 26 children map[string]*Tree 27 path []string 28 lock sync.RWMutex 29 } 30 31 // NewTree returns a new Tree for the given config structure. 32 func NewTree(name string, c *config.Config) *Tree { 33 return &Tree{config: c, name: name} 34 } 35 36 // NewEmptyTree returns a new tree that is empty (contains no configuration). 37 func NewEmptyTree() *Tree { 38 t := &Tree{config: &config.Config{}} 39 40 // We do this dummy load so that the tree is marked as "loaded". It 41 // should never fail because this is just about a no-op. If it does fail 42 // we panic so we can know its a bug. 43 if err := t.Load(nil, GetModeGet); err != nil { 44 panic(err) 45 } 46 47 return t 48 } 49 50 // NewTreeModule is like NewTree except it parses the configuration in 51 // the directory and gives it a specific name. Use a blank name "" to specify 52 // the root module. 53 func NewTreeModule(name, dir string) (*Tree, error) { 54 c, err := config.LoadDir(dir) 55 if err != nil { 56 return nil, err 57 } 58 59 return NewTree(name, c), nil 60 } 61 62 // Config returns the configuration for this module. 63 func (t *Tree) Config() *config.Config { 64 return t.config 65 } 66 67 // Child returns the child with the given path (by name). 68 func (t *Tree) Child(path []string) *Tree { 69 if len(path) == 0 { 70 return t 71 } 72 73 c := t.Children()[path[0]] 74 if c == nil { 75 return nil 76 } 77 78 return c.Child(path[1:]) 79 } 80 81 // Children returns the children of this tree (the modules that are 82 // imported by this root). 83 // 84 // This will only return a non-nil value after Load is called. 85 func (t *Tree) Children() map[string]*Tree { 86 t.lock.RLock() 87 defer t.lock.RUnlock() 88 return t.children 89 } 90 91 // Loaded says whether or not this tree has been loaded or not yet. 92 func (t *Tree) Loaded() bool { 93 t.lock.RLock() 94 defer t.lock.RUnlock() 95 return t.children != nil 96 } 97 98 // Modules returns the list of modules that this tree imports. 99 // 100 // This is only the imports of _this_ level of the tree. To retrieve the 101 // full nested imports, you'll have to traverse the tree. 102 func (t *Tree) Modules() []*Module { 103 result := make([]*Module, len(t.config.Modules)) 104 for i, m := range t.config.Modules { 105 result[i] = &Module{ 106 Name: m.Name, 107 Source: m.Source, 108 } 109 } 110 111 return result 112 } 113 114 // Name returns the name of the tree. This will be "<root>" for the root 115 // tree and then the module name given for any children. 116 func (t *Tree) Name() string { 117 if t.name == "" { 118 return RootName 119 } 120 121 return t.name 122 } 123 124 // Load loads the configuration of the entire tree. 125 // 126 // The parameters are used to tell the tree where to find modules and 127 // whether it can download/update modules along the way. 128 // 129 // Calling this multiple times will reload the tree. 130 // 131 // Various semantic-like checks are made along the way of loading since 132 // module trees inherently require the configuration to be in a reasonably 133 // sane state: no circular dependencies, proper module sources, etc. A full 134 // suite of validations can be done by running Validate (after loading). 135 func (t *Tree) Load(s getter.Storage, mode GetMode) error { 136 t.lock.Lock() 137 defer t.lock.Unlock() 138 139 // Reset the children if we have any 140 t.children = nil 141 142 modules := t.Modules() 143 children := make(map[string]*Tree) 144 145 // Go through all the modules and get the directory for them. 146 for _, m := range modules { 147 if _, ok := children[m.Name]; ok { 148 return fmt.Errorf( 149 "module %s: duplicated. module names must be unique", m.Name) 150 } 151 152 // Determine the path to this child 153 path := make([]string, len(t.path), len(t.path)+1) 154 copy(path, t.path) 155 path = append(path, m.Name) 156 157 // Split out the subdir if we have one 158 source, subDir := getter.SourceDirSubdir(m.Source) 159 160 source, err := getter.Detect(source, t.config.Dir, getter.Detectors) 161 if err != nil { 162 return fmt.Errorf("module %s: %s", m.Name, err) 163 } 164 165 // Check if the detector introduced something new. 166 source, subDir2 := getter.SourceDirSubdir(source) 167 if subDir2 != "" { 168 subDir = filepath.Join(subDir2, subDir) 169 } 170 171 // Get the directory where this module is so we can load it 172 key := strings.Join(path, ".") 173 key = "root." + key 174 dir, ok, err := getStorage(s, key, source, mode) 175 if err != nil { 176 return err 177 } 178 if !ok { 179 return fmt.Errorf( 180 "module %s: not found, may need to be downloaded using 'terraform get'", m.Name) 181 } 182 183 // If we have a subdirectory, then merge that in 184 if subDir != "" { 185 dir = filepath.Join(dir, subDir) 186 } 187 188 // Load the configurations.Dir(source) 189 children[m.Name], err = NewTreeModule(m.Name, dir) 190 if err != nil { 191 return fmt.Errorf( 192 "module %s: %s", m.Name, err) 193 } 194 195 // Set the path of this child 196 children[m.Name].path = path 197 } 198 199 // Go through all the children and load them. 200 for _, c := range children { 201 if err := c.Load(s, mode); err != nil { 202 return err 203 } 204 } 205 206 // Set our tree up 207 t.children = children 208 209 return nil 210 } 211 212 // Path is the full path to this tree. 213 func (t *Tree) Path() []string { 214 return t.path 215 } 216 217 // String gives a nice output to describe the tree. 218 func (t *Tree) String() string { 219 var result bytes.Buffer 220 path := strings.Join(t.path, ", ") 221 if path != "" { 222 path = fmt.Sprintf(" (path: %s)", path) 223 } 224 result.WriteString(t.Name() + path + "\n") 225 226 cs := t.Children() 227 if cs == nil { 228 result.WriteString(" not loaded") 229 } else { 230 // Go through each child and get its string value, then indent it 231 // by two. 232 for _, c := range cs { 233 r := strings.NewReader(c.String()) 234 scanner := bufio.NewScanner(r) 235 for scanner.Scan() { 236 result.WriteString(" ") 237 result.WriteString(scanner.Text()) 238 result.WriteString("\n") 239 } 240 } 241 } 242 243 return result.String() 244 } 245 246 // Validate does semantic checks on the entire tree of configurations. 247 // 248 // This will call the respective config.Config.Validate() functions as well 249 // as verifying things such as parameters/outputs between the various modules. 250 // 251 // Load must be called prior to calling Validate or an error will be returned. 252 func (t *Tree) Validate() error { 253 if !t.Loaded() { 254 return fmt.Errorf("tree must be loaded before calling Validate") 255 } 256 257 // If something goes wrong, here is our error template 258 newErr := &TreeError{Name: []string{t.Name()}} 259 260 // Validate our configuration first. 261 if err := t.config.Validate(); err != nil { 262 newErr.Err = err 263 return newErr 264 } 265 266 // Get the child trees 267 children := t.Children() 268 269 // Validate all our children 270 for _, c := range children { 271 err := c.Validate() 272 if err == nil { 273 continue 274 } 275 276 verr, ok := err.(*TreeError) 277 if !ok { 278 // Unknown error, just return... 279 return err 280 } 281 282 // Append ourselves to the error and then return 283 verr.Name = append(verr.Name, t.Name()) 284 return verr 285 } 286 287 // Go over all the modules and verify that any parameters are valid 288 // variables into the module in question. 289 for _, m := range t.config.Modules { 290 tree, ok := children[m.Name] 291 if !ok { 292 // This should never happen because Load watches us 293 panic("module not found in children: " + m.Name) 294 } 295 296 // Build the variables that the module defines 297 requiredMap := make(map[string]struct{}) 298 varMap := make(map[string]struct{}) 299 for _, v := range tree.config.Variables { 300 varMap[v.Name] = struct{}{} 301 302 if v.Required() { 303 requiredMap[v.Name] = struct{}{} 304 } 305 } 306 307 // Compare to the keys in our raw config for the module 308 for k, _ := range m.RawConfig.Raw { 309 if _, ok := varMap[k]; !ok { 310 newErr.Err = fmt.Errorf( 311 "module %s: %s is not a valid parameter", 312 m.Name, k) 313 return newErr 314 } 315 316 // Remove the required 317 delete(requiredMap, k) 318 } 319 320 // If we have any required left over, they aren't set. 321 for k, _ := range requiredMap { 322 newErr.Err = fmt.Errorf( 323 "module %s: required variable %s not set", 324 m.Name, k) 325 return newErr 326 } 327 } 328 329 // Go over all the variables used and make sure that any module 330 // variables represent outputs properly. 331 for source, vs := range t.config.InterpolatedVariables() { 332 for _, v := range vs { 333 mv, ok := v.(*config.ModuleVariable) 334 if !ok { 335 continue 336 } 337 338 tree, ok := children[mv.Name] 339 if !ok { 340 // This should never happen because Load watches us 341 panic("module not found in children: " + mv.Name) 342 } 343 344 found := false 345 for _, o := range tree.config.Outputs { 346 if o.Name == mv.Field { 347 found = true 348 break 349 } 350 } 351 if !found { 352 newErr.Err = fmt.Errorf( 353 "%s: %s is not a valid output for module %s", 354 source, mv.Field, mv.Name) 355 return newErr 356 } 357 } 358 } 359 360 return nil 361 } 362 363 // TreeError is an error returned by Tree.Validate if an error occurs 364 // with validation. 365 type TreeError struct { 366 Name []string 367 Err error 368 } 369 370 func (e *TreeError) Error() string { 371 // Build up the name 372 var buf bytes.Buffer 373 for _, n := range e.Name { 374 buf.WriteString(n) 375 buf.WriteString(".") 376 } 377 buf.Truncate(buf.Len() - 1) 378 379 // Format the value 380 return fmt.Sprintf("module %s: %s", buf.String(), e.Err) 381 }