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