github.com/kevinklinger/open_terraform@v1.3.6/noninternal/earlyconfig/config.go (about) 1 package earlyconfig 2 3 import ( 4 "fmt" 5 "sort" 6 7 version "github.com/hashicorp/go-version" 8 "github.com/hashicorp/terraform-config-inspect/tfconfig" 9 "github.com/kevinklinger/open_terraform/noninternal/addrs" 10 "github.com/kevinklinger/open_terraform/noninternal/getproviders" 11 "github.com/kevinklinger/open_terraform/noninternal/moduledeps" 12 "github.com/kevinklinger/open_terraform/noninternal/plugin/discovery" 13 "github.com/kevinklinger/open_terraform/noninternal/tfdiags" 14 ) 15 16 // A Config is a node in the tree of modules within a configuration. 17 // 18 // The module tree is constructed by following ModuleCall instances recursively 19 // through the root module transitively into descendent modules. 20 type Config struct { 21 // RootModule points to the Config for the root module within the same 22 // module tree as this module. If this module _is_ the root module then 23 // this is self-referential. 24 Root *Config 25 26 // ParentModule points to the Config for the module that directly calls 27 // this module. If this is the root module then this field is nil. 28 Parent *Config 29 30 // Path is a sequence of module logical names that traverse from the root 31 // module to this config. Path is empty for the root module. 32 // 33 // This should only be used to display paths to the end-user in rare cases 34 // where we are talking about the static module tree, before module calls 35 // have been resolved. In most cases, an addrs.ModuleInstance describing 36 // a node in the dynamic module tree is better, since it will then include 37 // any keys resulting from evaluating "count" and "for_each" arguments. 38 Path addrs.Module 39 40 // ChildModules points to the Config for each of the direct child modules 41 // called from this module. The keys in this map match the keys in 42 // Module.ModuleCalls. 43 Children map[string]*Config 44 45 // Module points to the object describing the configuration for the 46 // various elements (variables, resources, etc) defined by this module. 47 Module *tfconfig.Module 48 49 // CallPos is the source position for the header of the module block that 50 // requested this module. 51 // 52 // This field is meaningless for the root module, where its contents are undefined. 53 CallPos tfconfig.SourcePos 54 55 // SourceAddr is the source address that the referenced module was requested 56 // from, as specified in configuration. 57 // 58 // This field is meaningless for the root module, where its contents are undefined. 59 SourceAddr addrs.ModuleSource 60 61 // Version is the specific version that was selected for this module, 62 // based on version constraints given in configuration. 63 // 64 // This field is nil if the module was loaded from a non-registry source, 65 // since versions are not supported for other sources. 66 // 67 // This field is meaningless for the root module, where it will always 68 // be nil. 69 Version *version.Version 70 } 71 72 // ProviderRequirements searches the full tree of modules under the receiver 73 // for both explicit and implicit dependencies on providers. 74 // 75 // The result is a full manifest of all of the providers that must be available 76 // in order to work with the receiving configuration. 77 // 78 // If the returned diagnostics includes errors then the resulting Requirements 79 // may be incomplete. 80 func (c *Config) ProviderRequirements() (getproviders.Requirements, tfdiags.Diagnostics) { 81 reqs := make(getproviders.Requirements) 82 diags := c.addProviderRequirements(reqs) 83 return reqs, diags 84 } 85 86 // addProviderRequirements is the main part of the ProviderRequirements 87 // implementation, gradually mutating a shared requirements object to 88 // eventually return. 89 func (c *Config) addProviderRequirements(reqs getproviders.Requirements) tfdiags.Diagnostics { 90 var diags tfdiags.Diagnostics 91 92 // First we'll deal with the requirements directly in _our_ module... 93 for localName, providerReqs := range c.Module.RequiredProviders { 94 var fqn addrs.Provider 95 if source := providerReqs.Source; source != "" { 96 addr, moreDiags := addrs.ParseProviderSourceString(source) 97 if moreDiags.HasErrors() { 98 diags = diags.Append(tfdiags.Sourceless( 99 tfdiags.Error, 100 "Invalid provider source address", 101 fmt.Sprintf("Invalid source %q for provider %q in %s", source, localName, c.Path), 102 )) 103 continue 104 } 105 fqn = addr 106 } 107 if fqn.IsZero() { 108 fqn = addrs.ImpliedProviderForUnqualifiedType(localName) 109 } 110 if _, ok := reqs[fqn]; !ok { 111 // We'll at least have an unconstrained dependency then, but might 112 // add to this in the loop below. 113 reqs[fqn] = nil 114 } 115 for _, constraintsStr := range providerReqs.VersionConstraints { 116 if constraintsStr != "" { 117 constraints, err := getproviders.ParseVersionConstraints(constraintsStr) 118 if err != nil { 119 diags = diags.Append(tfdiags.Sourceless( 120 tfdiags.Error, 121 "Invalid provider version constraint", 122 fmt.Sprintf("Provider %q in %s has invalid version constraint %q: %s.", localName, c.Path, constraintsStr, err), 123 )) 124 continue 125 } 126 reqs[fqn] = append(reqs[fqn], constraints...) 127 } 128 } 129 } 130 131 // ...and now we'll recursively visit all of the child modules to merge 132 // in their requirements too. 133 for _, childConfig := range c.Children { 134 moreDiags := childConfig.addProviderRequirements(reqs) 135 diags = diags.Append(moreDiags) 136 } 137 138 return diags 139 } 140 141 // ProviderDependencies is a deprecated variant of ProviderRequirements which 142 // uses the moduledeps models for representation. This is preserved to allow 143 // a gradual transition over to ProviderRequirements, but note that its 144 // support for fully-qualified provider addresses has some idiosyncracies. 145 func (c *Config) ProviderDependencies() (*moduledeps.Module, tfdiags.Diagnostics) { 146 var diags tfdiags.Diagnostics 147 148 var name string 149 if len(c.Path) > 0 { 150 name = c.Path[len(c.Path)-1] 151 } 152 153 ret := &moduledeps.Module{ 154 Name: name, 155 } 156 157 providers := make(moduledeps.Providers) 158 for name, reqs := range c.Module.RequiredProviders { 159 var fqn addrs.Provider 160 if source := reqs.Source; source != "" { 161 addr, parseDiags := addrs.ParseProviderSourceString(source) 162 if parseDiags.HasErrors() { 163 diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{ 164 Severity: tfconfig.DiagError, 165 Summary: "Invalid provider source", 166 Detail: fmt.Sprintf("Invalid source %q for provider", name), 167 })) 168 continue 169 } 170 fqn = addr 171 } 172 if fqn.IsZero() { 173 fqn = addrs.NewDefaultProvider(name) 174 } 175 var constraints version.Constraints 176 for _, reqStr := range reqs.VersionConstraints { 177 if reqStr != "" { 178 constraint, err := version.NewConstraint(reqStr) 179 if err != nil { 180 diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{ 181 Severity: tfconfig.DiagError, 182 Summary: "Invalid provider version constraint", 183 Detail: fmt.Sprintf("Invalid version constraint %q for provider %s.", reqStr, fqn.String()), 184 })) 185 continue 186 } 187 constraints = append(constraints, constraint...) 188 } 189 } 190 providers[fqn] = moduledeps.ProviderDependency{ 191 Constraints: discovery.NewConstraints(constraints), 192 Reason: moduledeps.ProviderDependencyExplicit, 193 } 194 } 195 ret.Providers = providers 196 197 childNames := make([]string, 0, len(c.Children)) 198 for name := range c.Children { 199 childNames = append(childNames, name) 200 } 201 sort.Strings(childNames) 202 203 for _, name := range childNames { 204 child, childDiags := c.Children[name].ProviderDependencies() 205 ret.Children = append(ret.Children, child) 206 diags = diags.Append(childDiags) 207 } 208 209 return ret, diags 210 }