ModulePackageLocation finds the package location for a specific module package version. The subdir parameter is the subdirectory path from the original module source address (e.g., from addrs.ModuleSourceRegistry.Subdir). This is passed separately because regaddr.ModulePackage represents only the p
(ctx context.Context, packageAddr regaddr.ModulePackage, version string, subdir string)
| 161 | // to report. Refer to the documentation of those types for information on |
| 162 | // how each variant should be used to actually install the package. |
| 163 | func (c *Client) ModulePackageLocation(ctx context.Context, packageAddr regaddr.ModulePackage, version string, subdir string) (PackageLocation, error) { |
| 164 | ctx, span := tracing.Tracer().Start(ctx, "Find Module Location", tracing.SpanAttributes( |
| 165 | traceattrs.OpenTofuModuleSource(packageAddr.String()), |
| 166 | traceattrs.OpenTofuModuleVersion(version), |
| 167 | )) |
| 168 | defer span.End() |
| 169 | |
| 170 | host := packageAddr.Host |
| 171 | baseURL, err := c.discoverBaseURL(ctx, host) |
| 172 | if err != nil { |
| 173 | return nil, err |
| 174 | } |
| 175 | |
| 176 | // Historical note: an older version of this client code accepted "version" |
| 177 | // being empty and constructed a different form of URL where the version |
| 178 | // component was completely omitted, but the documentation for the registry |
| 179 | // protocol doesn't define the meaning of a URL scheme like that and in |
| 180 | // practice the callers of the client always populate the version, so we |
| 181 | // don't support omitting that anymore: a version string is now always expected. |
| 182 | metadataURL := modulePackageEndpointURL(baseURL, packageAddr, version, "download") |
| 183 | log.Printf("[DEBUG] looking up module location from %q", metadataURL) |
| 184 | |
| 185 | req, err := retryablehttp.NewRequestWithContext(ctx, "GET", metadataURL.String(), nil) |
| 186 | if err != nil { |
| 187 | return nil, err |
| 188 | } |
| 189 | |
| 190 | req = req.WithContext(ctx) |
| 191 | |
| 192 | c.addRequestCreds(ctx, host, req.Request) |
| 193 | req.Header.Set(xTerraformVersion, tfVersion) |
| 194 | |
| 195 | resp, err := c.client.Do(req) |
| 196 | if err != nil { |
| 197 | return nil, err |
| 198 | } |
| 199 | defer resp.Body.Close() |
| 200 | |
| 201 | body, err := io.ReadAll(resp.Body) |
| 202 | if err != nil { |
| 203 | return nil, fmt.Errorf("error reading response body from registry: %w", err) |
| 204 | } |
| 205 | |
| 206 | switch resp.StatusCode { |
| 207 | case http.StatusOK: |
| 208 | var v response.ModuleLocationRegistryResp |
| 209 | if err := json.Unmarshal(body, &v); err != nil { |
| 210 | return nil, fmt.Errorf("module %q version %q failed to deserialize response body %s: %w", |
| 211 | packageAddr, version, body, err) |
| 212 | } |
| 213 | |
| 214 | if v.UseRegistryCredentials == nil { |
| 215 | // The registry has not opted in to "direct" installation, so we |
| 216 | // assume that it wants the old-style "indirect" behavior where |
| 217 | // the registry is essentially just a lookup table for |
| 218 | // go-getter-style source addresses, in which case the registry |
| 219 | // isn't involved in the final download step at all. |
| 220 |