kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/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 "kubeform.dev/terraform-backend-sdk/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 26 // Now that the config is built, we can connect the provider names to all 27 // the known types for validation. 28 cfg.resolveProviderTypes() 29 30 diags = append(diags, validateProviderConfigs(nil, cfg, false)...) 31 32 return cfg, diags 33 } 34 35 func buildChildModules(parent *Config, walker ModuleWalker) (map[string]*Config, hcl.Diagnostics) { 36 var diags hcl.Diagnostics 37 ret := map[string]*Config{} 38 39 calls := parent.Module.ModuleCalls 40 41 // We'll sort the calls by their local names so that they'll appear in a 42 // predictable order in any logging that's produced during the walk. 43 callNames := make([]string, 0, len(calls)) 44 for k := range calls { 45 callNames = append(callNames, k) 46 } 47 sort.Strings(callNames) 48 49 for _, callName := range callNames { 50 call := calls[callName] 51 path := make([]string, len(parent.Path)+1) 52 copy(path, parent.Path) 53 path[len(path)-1] = call.Name 54 55 req := ModuleRequest{ 56 Name: call.Name, 57 Path: path, 58 SourceAddr: call.SourceAddr, 59 SourceAddrRange: call.SourceAddrRange, 60 VersionConstraint: call.Version, 61 Parent: parent, 62 CallRange: call.DeclRange, 63 } 64 65 mod, ver, modDiags := walker.LoadModule(&req) 66 diags = append(diags, modDiags...) 67 if mod == nil { 68 // nil can be returned if the source address was invalid and so 69 // nothing could be loaded whatsoever. LoadModule should've 70 // returned at least one error diagnostic in that case. 71 continue 72 } 73 74 child := &Config{ 75 Parent: parent, 76 Root: parent.Root, 77 Path: path, 78 Module: mod, 79 CallRange: call.DeclRange, 80 SourceAddr: call.SourceAddr, 81 SourceAddrRange: call.SourceAddrRange, 82 Version: ver, 83 } 84 85 child.Children, modDiags = buildChildModules(child, walker) 86 diags = append(diags, modDiags...) 87 88 if mod.Backend != nil { 89 diags = diags.Append(&hcl.Diagnostic{ 90 Severity: hcl.DiagWarning, 91 Summary: "Backend configuration ignored", 92 Detail: "Any selected backend applies to the entire configuration, so Terraform expects provider configurations only in the root module.\n\nThis is a warning rather than an error because it's sometimes convenient to temporarily call a root module as a child module for testing purposes, but this backend configuration block will have no effect.", 93 Subject: mod.Backend.DeclRange.Ptr(), 94 }) 95 } 96 97 ret[call.Name] = child 98 } 99 100 return ret, diags 101 } 102 103 // A ModuleWalker knows how to find and load a child module given details about 104 // the module to be loaded and a reference to its partially-loaded parent 105 // Config. 106 type ModuleWalker interface { 107 // LoadModule finds and loads a requested child module. 108 // 109 // If errors are detected during loading, implementations should return them 110 // in the diagnostics object. If the diagnostics object contains any errors 111 // then the caller will tolerate the returned module being nil or incomplete. 112 // If no errors are returned, it should be non-nil and complete. 113 // 114 // Full validation need not have been performed but an implementation should 115 // ensure that the basic file- and module-validations performed by the 116 // LoadConfigDir function (valid syntax, no namespace collisions, etc) have 117 // been performed before returning a module. 118 LoadModule(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) 119 } 120 121 // ModuleWalkerFunc is an implementation of ModuleWalker that directly wraps 122 // a callback function, for more convenient use of that interface. 123 type ModuleWalkerFunc func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) 124 125 // LoadModule implements ModuleWalker. 126 func (f ModuleWalkerFunc) LoadModule(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) { 127 return f(req) 128 } 129 130 // ModuleRequest is used with the ModuleWalker interface to describe a child 131 // module that must be loaded. 132 type ModuleRequest struct { 133 // Name is the "logical name" of the module call within configuration. 134 // This is provided in case the name is used as part of a storage key 135 // for the module, but implementations must otherwise treat it as an 136 // opaque string. It is guaranteed to have already been validated as an 137 // HCL identifier and UTF-8 encoded. 138 Name string 139 140 // Path is a list of logical names that traverse from the root module to 141 // this module. This can be used, for example, to form a lookup key for 142 // each distinct module call in a configuration, allowing for multiple 143 // calls with the same name at different points in the tree. 144 Path addrs.Module 145 146 // SourceAddr is the source address string provided by the user in 147 // configuration. 148 SourceAddr addrs.ModuleSource 149 150 // SourceAddrRange is the source range for the SourceAddr value as it 151 // was provided in configuration. This can and should be used to generate 152 // diagnostics about the source address having invalid syntax, referring 153 // to a non-existent object, etc. 154 SourceAddrRange hcl.Range 155 156 // VersionConstraint is the version constraint applied to the module in 157 // configuration. This data structure includes the source range for 158 // the constraint, which can and should be used to generate diagnostics 159 // about constraint-related issues, such as constraints that eliminate all 160 // available versions of a module whose source is otherwise valid. 161 VersionConstraint VersionConstraint 162 163 // Parent is the partially-constructed module tree node that the loaded 164 // module will be added to. Callers may refer to any field of this 165 // structure except Children, which is still under construction when 166 // ModuleRequest objects are created and thus has undefined content. 167 // The main reason this is provided is so that full module paths can 168 // be constructed for uniqueness. 169 Parent *Config 170 171 // CallRange is the source range for the header of the "module" block 172 // in configuration that prompted this request. This can be used as the 173 // subject of an error diagnostic that relates to the module call itself, 174 // rather than to either its source address or its version number. 175 CallRange hcl.Range 176 } 177 178 // DisabledModuleWalker is a ModuleWalker that doesn't support 179 // child modules at all, and so will return an error if asked to load one. 180 // 181 // This is provided primarily for testing. There is no good reason to use this 182 // in the main application. 183 var DisabledModuleWalker ModuleWalker 184 185 func init() { 186 DisabledModuleWalker = ModuleWalkerFunc(func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) { 187 return nil, nil, hcl.Diagnostics{ 188 { 189 Severity: hcl.DiagError, 190 Summary: "Child modules are not supported", 191 Detail: "Child module calls are not allowed in this context.", 192 Subject: &req.CallRange, 193 }, 194 } 195 }) 196 }