diff --git a/cli/provider_cache.go b/cli/provider_cache.go index a8be208855..601f28ca6f 100644 --- a/cli/provider_cache.go +++ b/cli/provider_cache.go @@ -113,7 +113,11 @@ func InitProviderCacheServer(opts *options.TerragruntOptions) (*ProviderCache, e case *cliconfig.ProviderInstallationFilesystemMirror: providerHandlers = append(providerHandlers, handlers.NewProviderFilesystemMirrorHandler(providerService, cacheProviderHTTPStatusCode, method)) case *cliconfig.ProviderInstallationNetworkMirror: - providerHandlers = append(providerHandlers, handlers.NewProviderNetworkMirrorHandler(providerService, cacheProviderHTTPStatusCode, method, cliCfg.CredentialsSource())) + networkMirrorHandler, err := handlers.NewProviderNetworkMirrorHandler(providerService, cacheProviderHTTPStatusCode, method, cliCfg.CredentialsSource()) + if err != nil { + return nil, err + } + providerHandlers = append(providerHandlers, networkMirrorHandler) case *cliconfig.ProviderInstallationDirect: providerHandlers = append(providerHandlers, handlers.NewProviderDirectHandler(providerService, cacheProviderHTTPStatusCode, method, cliCfg.CredentialsSource())) directIsdefined = true diff --git a/terraform/cache/handlers/provider_direct.go b/terraform/cache/handlers/provider_direct.go index 67404e640a..040a1b6af5 100644 --- a/terraform/cache/handlers/provider_direct.go +++ b/terraform/cache/handlers/provider_direct.go @@ -81,7 +81,7 @@ func (handler *ProviderDirectHandler) GetPlatform(ctx echo.Context, provider *mo return err } - provider.ResponseBody = body + provider.ResponseBody = body.ResolveRelativeReferences(resp.Request.URL) handler.providerService.CacheProvider(ctx.Request().Context(), cacheRequestID, provider) return ctx.NoContent(handler.cacheProviderHTTPStatusCode) diff --git a/terraform/cache/handlers/provider_network_mirror.go b/terraform/cache/handlers/provider_network_mirror.go index 628563a758..499a3e27ae 100644 --- a/terraform/cache/handlers/provider_network_mirror.go +++ b/terraform/cache/handlers/provider_network_mirror.go @@ -3,6 +3,7 @@ package handlers import ( "fmt" "net/http" + "net/url" "path" "path/filepath" "strings" @@ -23,19 +24,24 @@ type ProviderNetworkMirrorHandler struct { *http.Client providerService *services.ProviderService cacheProviderHTTPStatusCode int - networkMirrorURL string + networkMirrorURL *url.URL credsSource *cliconfig.CredentialsSource } -func NewProviderNetworkMirrorHandler(providerService *services.ProviderService, cacheProviderHTTPStatusCode int, networkMirror *cliconfig.ProviderInstallationNetworkMirror, credsSource *cliconfig.CredentialsSource) ProviderHandler { +func NewProviderNetworkMirrorHandler(providerService *services.ProviderService, cacheProviderHTTPStatusCode int, networkMirror *cliconfig.ProviderInstallationNetworkMirror, credsSource *cliconfig.CredentialsSource) (ProviderHandler, error) { + networkMirrorURL, err := url.Parse(networkMirror.URL) + if err != nil { + return nil, errors.WithStackTrace(err) + } + return &ProviderNetworkMirrorHandler{ CommonProviderHandler: NewCommonProviderHandler(networkMirror.Include, networkMirror.Exclude), Client: &http.Client{}, providerService: providerService, cacheProviderHTTPStatusCode: cacheProviderHTTPStatusCode, - networkMirrorURL: networkMirror.URL, + networkMirrorURL: networkMirrorURL, credsSource: credsSource, - } + }, nil } func (handler *ProviderNetworkMirrorHandler) String() string { @@ -90,14 +96,12 @@ func (handler *ProviderNetworkMirrorHandler) GetPlatform(ctx echo.Context, provi } if archive, ok := mirrorData.Archives[provider.Platform()]; ok { - if !strings.HasPrefix(archive.URL, "http") { - archive.URL = fmt.Sprintf("%s/%s", strings.TrimRight(handler.networkMirrorURL, "/"), path.Join(provider.RegistryName, provider.Namespace, provider.Name, archive.URL)) - } - - provider.ResponseBody = &models.ResponseBody{ + provider.ResponseBody = (&models.ResponseBody{ Filename: filepath.Base(archive.URL), DownloadURL: archive.URL, - } + }).ResolveRelativeReferences(handler.networkMirrorURL.ResolveReference(&url.URL{ + Path: path.Join(handler.networkMirrorURL.Path, provider.Address()), + })) } else { return ctx.NoContent(http.StatusNotFound) } @@ -113,7 +117,7 @@ func (handler *ProviderNetworkMirrorHandler) Download(ctx echo.Context, provider } func (handler *ProviderNetworkMirrorHandler) do(ctx echo.Context, method, reqPath string, value any) error { - reqURL := fmt.Sprintf("%s/%s", strings.TrimRight(handler.networkMirrorURL, "/"), reqPath) + reqURL := fmt.Sprintf("%s/%s", strings.TrimRight(handler.networkMirrorURL.String(), "/"), reqPath) req, err := http.NewRequestWithContext(ctx.Request().Context(), method, reqURL, nil) if err != nil { diff --git a/terraform/cache/models/helper.go b/terraform/cache/models/helper.go new file mode 100644 index 0000000000..7ed1507abe --- /dev/null +++ b/terraform/cache/models/helper.go @@ -0,0 +1,14 @@ +package models + +import ( + "net/url" + "path" + "strings" +) + +func resolveRelativeReference(base *url.URL, link string) string { + if link != "" && !strings.HasPrefix(link, base.Scheme) { + link = base.ResolveReference(&url.URL{Path: path.Join(base.Path, link)}).String() + } + return link +} diff --git a/terraform/cache/models/provider.go b/terraform/cache/models/provider.go index 3cbdb37296..7070373135 100644 --- a/terraform/cache/models/provider.go +++ b/terraform/cache/models/provider.go @@ -2,6 +2,7 @@ package models import ( "fmt" + "net/url" "path" "strings" ) @@ -73,6 +74,13 @@ type ResponseBody struct { SigningKeys SigningKeyList `json:"signing_keys,omitempty"` } +func (body ResponseBody) ResolveRelativeReferences(base *url.URL) *ResponseBody { + body.DownloadURL = resolveRelativeReference(base, body.DownloadURL) + body.SHA256SumsSignatureURL = resolveRelativeReference(base, body.SHA256SumsSignatureURL) + body.SHA256SumsURL = resolveRelativeReference(base, body.SHA256SumsURL) + return &body +} + // Provider represents the details of the Terraform provider. type Provider struct { *ResponseBody diff --git a/terraform/cache/models/provider_test.go b/terraform/cache/models/provider_test.go new file mode 100644 index 0000000000..3b994b4b2b --- /dev/null +++ b/terraform/cache/models/provider_test.go @@ -0,0 +1,61 @@ +package models + +import ( + "fmt" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestResolveRelativeReferences(t *testing.T) { + t.Parallel() + + testCases := []struct { + baseURL string + body ResponseBody + expectedResolved ResponseBody + }{ + { + "https://releases.hashicorp.com/terraform-provider-local/2.5.1", + ResponseBody{ + DownloadURL: "terraform-provider-local_2.5.1_darwin_amd64.zip", + SHA256SumsURL: "terraform-provider-local_2.5.1_SHA256SUMS", + SHA256SumsSignatureURL: "terraform-provider-local_2.5.1_SHA256SUMS.72D7468F.sig", + }, + ResponseBody{ + DownloadURL: "https://releases.hashicorp.com/terraform-provider-local/2.5.1/terraform-provider-local_2.5.1_darwin_amd64.zip", + SHA256SumsURL: "https://releases.hashicorp.com/terraform-provider-local/2.5.1/terraform-provider-local_2.5.1_SHA256SUMS", + SHA256SumsSignatureURL: "https://releases.hashicorp.com/terraform-provider-local/2.5.1/terraform-provider-local_2.5.1_SHA256SUMS.72D7468F.sig", + }, + }, + { + "https://somehost.com", + ResponseBody{ + DownloadURL: "https://releases.hashicorp.com/terraform-provider-local/2.5.1/terraform-provider-local_2.5.1_darwin_amd64.zip", + SHA256SumsURL: "https://releases.hashicorp.com/terraform-provider-local/2.5.1/terraform-provider-local_2.5.1_SHA256SUMS", + SHA256SumsSignatureURL: "https://releases.hashicorp.com/terraform-provider-local/2.5.1/terraform-provider-local_2.5.1_SHA256SUMS.72D7468F.sig", + }, + ResponseBody{ + DownloadURL: "https://releases.hashicorp.com/terraform-provider-local/2.5.1/terraform-provider-local_2.5.1_darwin_amd64.zip", + SHA256SumsURL: "https://releases.hashicorp.com/terraform-provider-local/2.5.1/terraform-provider-local_2.5.1_SHA256SUMS", + SHA256SumsSignatureURL: "https://releases.hashicorp.com/terraform-provider-local/2.5.1/terraform-provider-local_2.5.1_SHA256SUMS.72D7468F.sig", + }, + }, + } + + for i, testCase := range testCases { + testCase := testCase + + t.Run(fmt.Sprintf("testCase-%d", i), func(t *testing.T) { + t.Parallel() + + baseURL, err := url.Parse(testCase.baseURL) + require.NoError(t, err) + + actualResolved := testCase.body.ResolveRelativeReferences(baseURL) + assert.Equal(t, testCase.expectedResolved, *actualResolved) + }) + } +}