Files
opentf/internal/command/e2etest/provider_network_mirror_test.go
2025-10-18 11:42:25 +03:00

149 lines
6.3 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package e2etest
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/opentofu/opentofu/internal/e2e"
)
// TestProviderNetworkMirrorRetries checks that the retries configuration for downloading the
// provider from a network mirror source is handled correctly alone and also together with
// the TF_PROVIDER_DOWNLOAD_RETRY env var.
//
// This is testing the same thing as TestInitProviderSourceForCLIConfigLocationWithRetries but
// it's an e2e test instead because there is no way to inject the TLS certificate properly into
// the underlying implementation that talks with a remote network mirror source.
func TestProviderNetworkMirrorRetries(t *testing.T) {
// Our typical rule for external service access in e2etests is that it's okay
// to access servers run by the OpenTofu project when TF_ACC=1 is set in the
// environment. However, this particular test is checking a network mirror source
// that needs to be configured with a TLS certificate and that certificate to be given
// in the `tofu` child process env vars to be used for talking with the server.
// The entirety of this process can be done without actual network access so it can run
// without the TF_ACC=1.
//
// We restrict this for only linux_amd64 to make it easier to maintain since the whole purpose
// of the test has nothing to do with the actual platform it runs on.
//
// The fake server uses a locally-generated TLS certificate and so we'll need to
// override the trusted certs for the child process so it can be used successfully.
// The Go toolchain only allows doing that by environment variable on Unix systems
// other than macOS.
// Additionally, for ease of maintenance, the stubbed data inside this test is only
// for linux_amd64 so we cannot run this for other platforms that still support the
// previously mentioned limitation.
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
t.Skip("this test is suitable only for linux_amd64")
}
networkMirrorHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/example.com/test/test/index.json":
w.Header().Add("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"versions":{"0.0.1":{}}}`))
return
case "/example.com/test/test/0.0.1.json":
w.Header().Add("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"archives": {"linux_amd64": {"url": "terraform-provider-test_0.0.1_linux_amd64.zip","hashes": []}}}`))
return
case "/example.com/test/test/terraform-provider-test_0.0.1_linux_amd64.zip":
w.WriteHeader(http.StatusInternalServerError)
return
default:
w.WriteHeader(http.StatusNotFound)
}
})
cases := map[string]struct {
tofurcRetriesConfigEntry string
envVars map[string]string
expectedErrMsg string
}{
"no tofurc.network_mirror.download_retry_count, no TF_PROVIDER_DOWNLOAD_RETRY, default TF_PROVIDER_DOWNLOAD_RETRY used": {
tofurcRetriesConfigEntry: "",
envVars: nil,
expectedErrMsg: "/example.com/test/test/terraform-provider-test_0.0.1_linux_amd64.zip giving up after 3 attempt(s)",
},
"no tofurc.network_mirror.download_retry_count, TF_PROVIDER_DOWNLOAD_RETRY defined, TF_PROVIDER_DOWNLOAD_RETRY used": {
tofurcRetriesConfigEntry: "",
envVars: map[string]string{
"TF_PROVIDER_DOWNLOAD_RETRY": "1",
},
expectedErrMsg: "/example.com/test/test/terraform-provider-test_0.0.1_linux_amd64.zip giving up after 2 attempt(s)",
},
"defined tofurc.network_mirror.download_retry_count as 0, no TF_PROVIDER_DOWNLOAD_RETRY, tofurc used": {
tofurcRetriesConfigEntry: "download_retry_count = 0",
envVars: nil,
expectedErrMsg: "/example.com/test/test/terraform-provider-test_0.0.1_linux_amd64.zip giving up after 1 attempt(s)",
},
"defined tofurc.network_mirror.download_retry_count as 1, no TF_PROVIDER_DOWNLOAD_RETRY, tofurc used": {
tofurcRetriesConfigEntry: "download_retry_count = 1",
envVars: nil,
expectedErrMsg: "/example.com/test/test/terraform-provider-test_0.0.1_linux_amd64.zip giving up after 2 attempt(s)",
},
"defined tofurc.network_mirror.download_retry_count as 1, TF_PROVIDER_DOWNLOAD_RETRY defined as 2, tofurc used": {
tofurcRetriesConfigEntry: "download_retry_count = 1",
envVars: map[string]string{
"TF_PROVIDER_DOWNLOAD_RETRY": "2",
},
expectedErrMsg: "/example.com/test/test/terraform-provider-test_0.0.1_linux_amd64.zip giving up after 2 attempt(s)",
},
}
for name, tt := range cases {
t.Run(name, func(t *testing.T) {
server := httptest.NewTLSServer(networkMirrorHandler)
defer server.Close()
registryAddr := server.URL
// We need to pass both our fake registry server's certificate and the CLI config
// for interacting with it to the child process using some temporary files on disk.
tempDir := t.TempDir()
certFile := filepath.Join(tempDir, "testserver.crt")
if err := writeCertificatePEMFile(certFile, server.Certificate()); err != nil {
t.Fatalf("failed to create temporary certificate file: %s", err)
}
cliConfigFile := filepath.Join(tempDir, "cliconfig.tfrc")
cliConfigSrc := fmt.Sprintf(`
provider_installation {
network_mirror {
url = "%s"
%s
}
}
`, registryAddr, tt.tofurcRetriesConfigEntry)
if err := os.WriteFile(cliConfigFile, []byte(cliConfigSrc), os.ModePerm); err != nil {
t.Fatalf("failed to create temporary CLI configuration file: %s", err)
}
dataDir := filepath.Join(tempDir, ".terraform")
tf := e2e.NewBinary(t, tofuBin, "testdata/provider-network-mirror")
tf.AddEnv("SSL_CERT_FILE=" + certFile)
tf.AddEnv("TF_CLI_CONFIG_FILE=" + cliConfigFile)
tf.AddEnv("TF_DATA_DIR=" + dataDir)
for k, v := range tt.envVars {
tf.AddEnv(fmt.Sprintf("%s=%s", k, v))
}
_, stderr, err := tf.Run("init", "-backend=false")
if err == nil {
t.Fatalf("expected `tofu init` to fail but got no error")
}
t.Logf("stderr:\n%s", stderr)
cleanStderr := SanitizeStderr(stderr)
if contains := tt.expectedErrMsg; !strings.Contains(cleanStderr, contains) {
t.Fatalf("expected the error from the installation to contain %q but it doesn't", contains)
}
})
}
}