github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/configs/config_build.go (about) 1 package configs 2 3 import ( 4 "sort" 5 6 version "github.com/hashicorp/go-version" 7 "github.com/hashicorp/hcl/v2" 8 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 9 ) 10 11 // BuildConfig constructs a Config from a root module by loading all of its 12 // descendent modules via the given ModuleWalker. 13 // 14 // The result is a module tree that has so far only had basic module- and 15 // file-level invariants validated. If the returned diagnostics contains errors, 16 // the returned module tree may be incomplete but can still be used carefully 17 // for static analysis. 18 func BuildConfig(root *Module, walker ModuleWalker) (*Config, hcl.Diagnostics) { 19 var diags hcl.Diagnostics 20 cfg := &Config{ 21 Module: root, 22 } 23 cfg.Root = cfg // Root module is self-referential. 24 cfg.Children, diags = buildChildModules(cfg, walker) 25 return cfg, diags 26 } 27 28 func buildChildModules(parent *Config, walker ModuleWalker) (map[string]*Config, hcl.Diagnostics) { 29 var diags hcl.Diagnostics 30 ret := map[string]*Config{} 31 32 calls := parent.Module.ModuleCalls 33 34 // We'll sort the calls by their local names so that they'll appear in a 35 // predictable order in any logging that's produced during the walk. 36 callNames := make([]string, 0, len(calls)) 37 for k := range calls { 38 callNames = append(callNames, k) 39 } 40 sort.Strings(callNames) 41 42 for _, callName := range callNames { 43 call := calls[callName] 44 path := make([]string, len(parent.Path)+1) 45 copy(path, parent.Path) 46 path[len(path)-1] = call.Name 47 48 req := ModuleRequest{ 49 Name: call.Name, 50 Path: path, 51 SourceAddr: call.SourceAddr, 52 SourceAddrRange: call.SourceAddrRange, 53 VersionConstraint: call.Version, 54 Parent: parent, 55 CallRange: call.DeclRange, 56 } 57 58 mod, ver, modDiags := walker.LoadModule(&req) 59 diags = append(diags, modDiags...) 60 if mod == nil { 61 // nil can be returned if the source address was invalid and so 62 // nothing could be loaded whatsoever. LoadModule should've 63 // returned at least one error diagnostic in that case. 64 continue 65 } 66 67 child := &Config{ 68 Parent: parent, 69 Root: parent.Root, 70 Path: path, 71 Module: mod, 72 CallRange: call.DeclRange, 73 SourceAddr: call.SourceAddr, 74 SourceAddrRange: call.SourceAddrRange, 75 Version: ver, 76 } 77 78 child.Children, modDiags = buildChildModules(child, walker) 79 diags = append(diags, modDiags...) 80 81 ret[call.Name] = child 82 } 83 84 return ret, diags 85 } 86 87 // A ModuleWalker knows how to find and load a child module given details about 88 // the module to be loaded and a reference to its partially-loaded parent 89 // Config. 90 type ModuleWalker interface { 91 // LoadModule finds and loads a requested child module. 92 // 93 // If errors are detected during loading, implementations should return them 94 // in the diagnostics object. If the diagnostics object contains any errors 95 // then the caller will tolerate the returned module being nil or incomplete. 96 // If no errors are returned, it should be non-nil and complete. 97 // 98 // Full validation need not have been performed but an implementation should 99 // ensure that the basic file- and module-validations performed by the 100 // LoadConfigDir function (valid syntax, no namespace collisions, etc) have 101 // been performed before returning a module. 102 LoadModule(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) 103 } 104 105 // ModuleWalkerFunc is an implementation of ModuleWalker that directly wraps 106 // a callback function, for more convenient use of that interface. 107 type ModuleWalkerFunc func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) 108 109 // LoadModule implements ModuleWalker. 110 func (f ModuleWalkerFunc) LoadModule(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) { 111 return f(req) 112 } 113 114 // ModuleRequest is used with the ModuleWalker interface to describe a child 115 // module that must be loaded. 116 type ModuleRequest struct { 117 // Name is the "logical name" of the module call within configuration. 118 // This is provided in case the name is used as part of a storage key 119 // for the module, but implementations must otherwise treat it as an 120 // opaque string. It is guaranteed to have already been validated as an 121 // HCL identifier and UTF-8 encoded. 122 Name string 123 124 // Path is a list of logical names that traverse from the root module to 125 // this module. This can be used, for example, to form a lookup key for 126 // each distinct module call in a configuration, allowing for multiple 127 // calls with the same name at different points in the tree. 128 Path addrs.Module 129 130 // SourceAddr is the source address string provided by the user in 131 // configuration. 132 SourceAddr string 133 134 // SourceAddrRange is the source range for the SourceAddr value as it 135 // was provided in configuration. This can and should be used to generate 136 // diagnostics about the source address having invalid syntax, referring 137 // to a non-existent object, etc. 138 SourceAddrRange hcl.Range 139 140 // VersionConstraint is the version constraint applied to the module in 141 // configuration. This data structure includes the source range for 142 // the constraint, which can and should be used to generate diagnostics 143 // about constraint-related issues, such as constraints that eliminate all 144 // available versions of a module whose source is otherwise valid. 145 VersionConstraint VersionConstraint 146 147 // Parent is the partially-constructed module tree node that the loaded 148 // module will be added to. Callers may refer to any field of this 149 // structure except Children, which is still under construction when 150 // ModuleRequest objects are created and thus has undefined content. 151 // The main reason this is provided is so that full module paths can 152 // be constructed for uniqueness. 153 Parent *Config 154 155 // CallRange is the source range for the header of the "module" block 156 // in configuration that prompted this request. This can be used as the 157 // subject of an error diagnostic that relates to the module call itself, 158 // rather than to either its source address or its version number. 159 CallRange hcl.Range 160 }