github.com/pulumi/terraform@v1.4.0/pkg/earlyconfig/config_build.go (about) 1 package earlyconfig 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 version "github.com/hashicorp/go-version" 9 "github.com/hashicorp/terraform-config-inspect/tfconfig" 10 "github.com/pulumi/terraform/pkg/addrs" 11 "github.com/pulumi/terraform/pkg/tfdiags" 12 ) 13 14 // BuildConfig constructs a Config from a root module by loading all of its 15 // descendent modules via the given ModuleWalker. 16 func BuildConfig(root *tfconfig.Module, walker ModuleWalker) (*Config, tfdiags.Diagnostics) { 17 var diags tfdiags.Diagnostics 18 cfg := &Config{ 19 Module: root, 20 } 21 cfg.Root = cfg // Root module is self-referential. 22 cfg.Children, diags = buildChildModules(cfg, walker) 23 return cfg, diags 24 } 25 26 func buildChildModules(parent *Config, walker ModuleWalker) (map[string]*Config, tfdiags.Diagnostics) { 27 var diags tfdiags.Diagnostics 28 ret := map[string]*Config{} 29 calls := parent.Module.ModuleCalls 30 31 // We'll sort the calls by their local names so that they'll appear in a 32 // predictable order in any logging that's produced during the walk. 33 callNames := make([]string, 0, len(calls)) 34 for k := range calls { 35 callNames = append(callNames, k) 36 } 37 sort.Strings(callNames) 38 39 for _, callName := range callNames { 40 call := calls[callName] 41 path := make([]string, len(parent.Path)+1) 42 copy(path, parent.Path) 43 path[len(path)-1] = call.Name 44 45 var vc version.Constraints 46 haveVersionArg := false 47 if strings.TrimSpace(call.Version) != "" { 48 haveVersionArg = true 49 50 var err error 51 vc, err = version.NewConstraint(call.Version) 52 if err != nil { 53 diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{ 54 Severity: tfconfig.DiagError, 55 Summary: "Invalid version constraint", 56 Detail: fmt.Sprintf("Module %q (declared at %s line %d) has invalid version constraint %q: %s.", callName, call.Pos.Filename, call.Pos.Line, call.Version, err), 57 })) 58 continue 59 } 60 } 61 62 var sourceAddr addrs.ModuleSource 63 var err error 64 if haveVersionArg { 65 sourceAddr, err = addrs.ParseModuleSourceRegistry(call.Source) 66 } else { 67 sourceAddr, err = addrs.ParseModuleSource(call.Source) 68 } 69 if err != nil { 70 if haveVersionArg { 71 diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{ 72 Severity: tfconfig.DiagError, 73 Summary: "Invalid registry module source address", 74 Detail: fmt.Sprintf("Module %q (declared at %s line %d) has invalid source address %q: %s.\n\nTerraform assumed that you intended a module registry source address because you also set the argument \"version\", which applies only to registry modules.", callName, call.Pos.Filename, call.Pos.Line, call.Source, err), 75 })) 76 } else { 77 diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{ 78 Severity: tfconfig.DiagError, 79 Summary: "Invalid module source address", 80 Detail: fmt.Sprintf("Module %q (declared at %s line %d) has invalid source address %q: %s.", callName, call.Pos.Filename, call.Pos.Line, call.Source, err), 81 })) 82 } 83 // If we didn't have a valid source address then we can't continue 84 // down the module tree with this one. 85 continue 86 } 87 88 req := ModuleRequest{ 89 Name: call.Name, 90 Path: path, 91 SourceAddr: sourceAddr, 92 VersionConstraints: vc, 93 Parent: parent, 94 CallPos: call.Pos, 95 } 96 97 mod, ver, modDiags := walker.LoadModule(&req) 98 diags = append(diags, modDiags...) 99 if mod == nil { 100 // nil can be returned if the source address was invalid and so 101 // nothing could be loaded whatsoever. LoadModule should've 102 // returned at least one error diagnostic in that case. 103 continue 104 } 105 106 child := &Config{ 107 Parent: parent, 108 Root: parent.Root, 109 Path: path, 110 Module: mod, 111 CallPos: call.Pos, 112 SourceAddr: sourceAddr, 113 Version: ver, 114 } 115 116 child.Children, modDiags = buildChildModules(child, walker) 117 diags = diags.Append(modDiags) 118 119 ret[call.Name] = child 120 } 121 122 return ret, diags 123 } 124 125 // ModuleRequest is used as part of the ModuleWalker interface used with 126 // function BuildConfig. 127 type ModuleRequest struct { 128 // Name is the "logical name" of the module call within configuration. 129 // This is provided in case the name is used as part of a storage key 130 // for the module, but implementations must otherwise treat it as an 131 // opaque string. It is guaranteed to have already been validated as an 132 // HCL identifier and UTF-8 encoded. 133 Name string 134 135 // Path is a list of logical names that traverse from the root module to 136 // this module. This can be used, for example, to form a lookup key for 137 // each distinct module call in a configuration, allowing for multiple 138 // calls with the same name at different points in the tree. 139 Path addrs.Module 140 141 // SourceAddr is the source address string provided by the user in 142 // configuration. 143 SourceAddr addrs.ModuleSource 144 145 // VersionConstraint is the version constraint applied to the module in 146 // configuration. 147 VersionConstraints version.Constraints 148 149 // Parent is the partially-constructed module tree node that the loaded 150 // module will be added to. Callers may refer to any field of this 151 // structure except Children, which is still under construction when 152 // ModuleRequest objects are created and thus has undefined content. 153 // The main reason this is provided is so that full module paths can 154 // be constructed for uniqueness. 155 Parent *Config 156 157 // CallRange is the source position for the header of the "module" block 158 // in configuration that prompted this request. 159 CallPos tfconfig.SourcePos 160 } 161 162 // ModuleWalker is an interface used with BuildConfig. 163 type ModuleWalker interface { 164 LoadModule(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) 165 } 166 167 // ModuleWalkerFunc is an implementation of ModuleWalker that directly wraps 168 // a callback function, for more convenient use of that interface. 169 type ModuleWalkerFunc func(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) 170 171 func (f ModuleWalkerFunc) LoadModule(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) { 172 return f(req) 173 }