2017-11-27 22:48:39 +01:00
|
|
|
package registry
|
|
|
|
|
|
|
|
import (
|
2020-02-13 22:54:27 +01:00
|
|
|
"context"
|
|
|
|
"net/http"
|
2017-11-27 22:48:39 +01:00
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
2020-03-05 17:37:06 +01:00
|
|
|
"time"
|
2017-11-27 22:48:39 +01:00
|
|
|
|
|
|
|
version "github.com/hashicorp/go-version"
|
2019-10-11 11:34:26 +02:00
|
|
|
"github.com/hashicorp/terraform-svchost/disco"
|
2021-05-17 18:54:53 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/httpclient"
|
2021-05-17 18:45:36 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/registry/regsrc"
|
|
|
|
"github.com/hashicorp/terraform/internal/registry/test"
|
2019-10-11 11:34:26 +02:00
|
|
|
tfversion "github.com/hashicorp/terraform/version"
|
2017-11-27 22:48:39 +01:00
|
|
|
)
|
|
|
|
|
2020-02-13 22:54:27 +01:00
|
|
|
func TestConfigureDiscoveryRetry(t *testing.T) {
|
|
|
|
t.Run("default retry", func(t *testing.T) {
|
|
|
|
if discoveryRetry != defaultRetry {
|
|
|
|
t.Fatalf("expected retry %q, got %q", defaultRetry, discoveryRetry)
|
|
|
|
}
|
|
|
|
|
|
|
|
rc := NewClient(nil, nil)
|
|
|
|
if rc.client.RetryMax != defaultRetry {
|
|
|
|
t.Fatalf("expected client retry %q, got %q",
|
|
|
|
defaultRetry, rc.client.RetryMax)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("configured retry", func(t *testing.T) {
|
2020-03-03 20:52:39 +01:00
|
|
|
defer func(retryEnv string) {
|
|
|
|
os.Setenv(registryDiscoveryRetryEnvName, retryEnv)
|
2020-02-13 22:54:27 +01:00
|
|
|
discoveryRetry = defaultRetry
|
2020-03-03 20:52:39 +01:00
|
|
|
}(os.Getenv(registryDiscoveryRetryEnvName))
|
2020-02-13 22:54:27 +01:00
|
|
|
os.Setenv(registryDiscoveryRetryEnvName, "2")
|
|
|
|
|
|
|
|
configureDiscoveryRetry()
|
|
|
|
expected := 2
|
|
|
|
if discoveryRetry != expected {
|
|
|
|
t.Fatalf("expected retry %q, got %q",
|
|
|
|
expected, discoveryRetry)
|
|
|
|
}
|
|
|
|
|
|
|
|
rc := NewClient(nil, nil)
|
|
|
|
if rc.client.RetryMax != expected {
|
|
|
|
t.Fatalf("expected client retry %q, got %q",
|
|
|
|
expected, rc.client.RetryMax)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-03-05 17:37:06 +01:00
|
|
|
func TestConfigureRegistryClientTimeout(t *testing.T) {
|
|
|
|
t.Run("default timeout", func(t *testing.T) {
|
|
|
|
if requestTimeout != defaultRequestTimeout {
|
|
|
|
t.Fatalf("expected timeout %q, got %q",
|
|
|
|
defaultRequestTimeout.String(), requestTimeout.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
rc := NewClient(nil, nil)
|
|
|
|
if rc.client.HTTPClient.Timeout != defaultRequestTimeout {
|
|
|
|
t.Fatalf("expected client timeout %q, got %q",
|
|
|
|
defaultRequestTimeout.String(), rc.client.HTTPClient.Timeout.String())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("configured timeout", func(t *testing.T) {
|
|
|
|
defer func(timeoutEnv string) {
|
|
|
|
os.Setenv(registryClientTimeoutEnvName, timeoutEnv)
|
|
|
|
requestTimeout = defaultRequestTimeout
|
|
|
|
}(os.Getenv(registryClientTimeoutEnvName))
|
|
|
|
os.Setenv(registryClientTimeoutEnvName, "20")
|
|
|
|
|
|
|
|
configureRequestTimeout()
|
|
|
|
expected := 20 * time.Second
|
|
|
|
if requestTimeout != expected {
|
|
|
|
t.Fatalf("expected timeout %q, got %q",
|
|
|
|
expected, requestTimeout.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
rc := NewClient(nil, nil)
|
|
|
|
if rc.client.HTTPClient.Timeout != expected {
|
|
|
|
t.Fatalf("expected client timeout %q, got %q",
|
|
|
|
expected, rc.client.HTTPClient.Timeout.String())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-11-27 22:48:39 +01:00
|
|
|
func TestLookupModuleVersions(t *testing.T) {
|
|
|
|
server := test.Registry()
|
|
|
|
defer server.Close()
|
|
|
|
|
2018-07-05 21:28:29 +02:00
|
|
|
client := NewClient(test.Disco(server), nil)
|
2017-11-27 22:48:39 +01:00
|
|
|
|
|
|
|
// test with and without a hostname
|
|
|
|
for _, src := range []string{
|
|
|
|
"example.com/test-versions/name/provider",
|
|
|
|
"test-versions/name/provider",
|
|
|
|
} {
|
|
|
|
modsrc, err := regsrc.ParseModuleSource(src)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:09:16 +01:00
|
|
|
resp, err := client.ModuleVersions(context.Background(), modsrc)
|
2017-11-27 22:48:39 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Modules) != 1 {
|
|
|
|
t.Fatal("expected 1 module, got", len(resp.Modules))
|
|
|
|
}
|
|
|
|
|
|
|
|
mod := resp.Modules[0]
|
|
|
|
name := "test-versions/name/provider"
|
|
|
|
if mod.Source != name {
|
|
|
|
t.Fatalf("expected module name %q, got %q", name, mod.Source)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(mod.Versions) != 4 {
|
|
|
|
t.Fatal("expected 4 versions, got", len(mod.Versions))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, v := range mod.Versions {
|
|
|
|
_, err := version.NewVersion(v.Version)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("invalid version %q: %s", v.Version, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-18 00:31:05 +01:00
|
|
|
func TestInvalidRegistry(t *testing.T) {
|
|
|
|
server := test.Registry()
|
|
|
|
defer server.Close()
|
|
|
|
|
2018-07-05 21:28:29 +02:00
|
|
|
client := NewClient(test.Disco(server), nil)
|
2018-01-18 00:31:05 +01:00
|
|
|
|
|
|
|
src := "non-existent.localhost.localdomain/test-versions/name/provider"
|
|
|
|
modsrc, err := regsrc.ParseModuleSource(src)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:09:16 +01:00
|
|
|
if _, err := client.ModuleVersions(context.Background(), modsrc); err == nil {
|
2018-01-18 00:31:05 +01:00
|
|
|
t.Fatal("expected error")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-27 22:48:39 +01:00
|
|
|
func TestRegistryAuth(t *testing.T) {
|
|
|
|
server := test.Registry()
|
|
|
|
defer server.Close()
|
|
|
|
|
2018-07-05 21:28:29 +02:00
|
|
|
client := NewClient(test.Disco(server), nil)
|
2017-11-27 22:48:39 +01:00
|
|
|
|
|
|
|
src := "private/name/provider"
|
|
|
|
mod, err := regsrc.ParseModuleSource(src)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:09:16 +01:00
|
|
|
_, err = client.ModuleVersions(context.Background(), mod)
|
2018-10-31 16:45:03 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2017-11-27 22:48:39 +01:00
|
|
|
}
|
2021-11-01 21:09:16 +01:00
|
|
|
_, err = client.ModuleLocation(context.Background(), mod, "1.0.0")
|
2018-10-31 16:45:03 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2017-11-27 22:48:39 +01:00
|
|
|
}
|
|
|
|
|
2018-07-05 21:28:29 +02:00
|
|
|
// Also test without a credentials source
|
|
|
|
client.services.SetCredentialsSource(nil)
|
2017-11-27 22:48:39 +01:00
|
|
|
|
2018-10-31 16:45:03 +01:00
|
|
|
// both should fail without auth
|
2021-11-01 21:09:16 +01:00
|
|
|
_, err = client.ModuleVersions(context.Background(), mod)
|
2018-10-31 16:45:03 +01:00
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected error")
|
2017-11-27 22:48:39 +01:00
|
|
|
}
|
2021-11-01 21:09:16 +01:00
|
|
|
_, err = client.ModuleLocation(context.Background(), mod, "1.0.0")
|
2018-10-31 16:45:03 +01:00
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected error")
|
2017-11-27 22:48:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLookupModuleLocationRelative(t *testing.T) {
|
|
|
|
server := test.Registry()
|
|
|
|
defer server.Close()
|
|
|
|
|
2018-07-05 21:28:29 +02:00
|
|
|
client := NewClient(test.Disco(server), nil)
|
2017-11-27 22:48:39 +01:00
|
|
|
|
|
|
|
src := "relative/foo/bar"
|
|
|
|
mod, err := regsrc.ParseModuleSource(src)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:09:16 +01:00
|
|
|
got, err := client.ModuleLocation(context.Background(), mod, "0.2.0")
|
2017-11-27 22:48:39 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
want := server.URL + "/relative-path"
|
|
|
|
if got != want {
|
|
|
|
t.Errorf("wrong location %s; want %s", got, want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAccLookupModuleVersions(t *testing.T) {
|
|
|
|
if os.Getenv("TF_ACC") == "" {
|
|
|
|
t.Skip()
|
|
|
|
}
|
2018-07-05 21:28:29 +02:00
|
|
|
regDisco := disco.New()
|
2019-10-11 11:34:26 +02:00
|
|
|
regDisco.SetUserAgent(httpclient.TerraformUserAgent(tfversion.String()))
|
2017-11-27 22:48:39 +01:00
|
|
|
|
|
|
|
// test with and without a hostname
|
|
|
|
for _, src := range []string{
|
|
|
|
"terraform-aws-modules/vpc/aws",
|
|
|
|
regsrc.PublicRegistryHost.String() + "/terraform-aws-modules/vpc/aws",
|
|
|
|
} {
|
|
|
|
modsrc, err := regsrc.ParseModuleSource(src)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-07-05 21:28:29 +02:00
|
|
|
s := NewClient(regDisco, nil)
|
2021-11-01 21:09:16 +01:00
|
|
|
resp, err := s.ModuleVersions(context.Background(), modsrc)
|
2017-11-27 22:48:39 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resp.Modules) != 1 {
|
|
|
|
t.Fatal("expected 1 module, got", len(resp.Modules))
|
|
|
|
}
|
|
|
|
|
|
|
|
mod := resp.Modules[0]
|
|
|
|
name := "terraform-aws-modules/vpc/aws"
|
|
|
|
if mod.Source != name {
|
|
|
|
t.Fatalf("expected module name %q, got %q", name, mod.Source)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(mod.Versions) == 0 {
|
|
|
|
t.Fatal("expected multiple versions, got 0")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, v := range mod.Versions {
|
|
|
|
_, err := version.NewVersion(v.Version)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("invalid version %q: %s", v.Version, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-13 22:54:27 +01:00
|
|
|
// the error should reference the config source exactly, not the discovered path.
|
2017-11-27 22:48:39 +01:00
|
|
|
func TestLookupLookupModuleError(t *testing.T) {
|
|
|
|
server := test.Registry()
|
|
|
|
defer server.Close()
|
|
|
|
|
2018-07-05 21:28:29 +02:00
|
|
|
client := NewClient(test.Disco(server), nil)
|
2017-11-27 22:48:39 +01:00
|
|
|
|
2020-02-13 22:54:27 +01:00
|
|
|
// this should not be found in the registry
|
2017-11-27 22:48:39 +01:00
|
|
|
src := "bad/local/path"
|
|
|
|
mod, err := regsrc.ParseModuleSource(src)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2020-02-13 22:54:27 +01:00
|
|
|
// Instrument CheckRetry to make sure 404s are not retried
|
|
|
|
retries := 0
|
|
|
|
oldCheck := client.client.CheckRetry
|
|
|
|
client.client.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) {
|
|
|
|
if retries > 0 {
|
|
|
|
t.Fatal("retried after module not found")
|
|
|
|
}
|
|
|
|
retries++
|
|
|
|
return oldCheck(ctx, resp, err)
|
|
|
|
}
|
|
|
|
|
2021-11-01 21:09:16 +01:00
|
|
|
_, err = client.ModuleLocation(context.Background(), mod, "0.2.0")
|
2017-11-27 22:48:39 +01:00
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected error")
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for the exact quoted string to ensure we didn't prepend a hostname.
|
|
|
|
if !strings.Contains(err.Error(), `"bad/local/path"`) {
|
|
|
|
t.Fatal("error should not include the hostname. got:", err)
|
|
|
|
}
|
|
|
|
}
|
2018-07-27 20:59:03 +02:00
|
|
|
|
2020-02-13 22:54:27 +01:00
|
|
|
func TestLookupModuleRetryError(t *testing.T) {
|
|
|
|
server := test.RegistryRetryableErrorsServer()
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
client := NewClient(test.Disco(server), nil)
|
|
|
|
|
|
|
|
src := "example.com/test-versions/name/provider"
|
|
|
|
modsrc, err := regsrc.ParseModuleSource(src)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2021-11-01 21:09:16 +01:00
|
|
|
resp, err := client.ModuleVersions(context.Background(), modsrc)
|
2020-02-13 22:54:27 +01:00
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected requests to exceed retry", err)
|
|
|
|
}
|
|
|
|
if resp != nil {
|
|
|
|
t.Fatal("unexpected response", *resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify maxRetryErrorHandler handler returned the error
|
|
|
|
if !strings.Contains(err.Error(), "the request failed after 2 attempts, please try again later") {
|
|
|
|
t.Fatal("unexpected error, got:", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-19 15:20:10 +01:00
|
|
|
func TestLookupModuleNoRetryError(t *testing.T) {
|
|
|
|
// Disable retries
|
|
|
|
discoveryRetry = 0
|
|
|
|
defer configureDiscoveryRetry()
|
|
|
|
|
|
|
|
server := test.RegistryRetryableErrorsServer()
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
client := NewClient(test.Disco(server), nil)
|
|
|
|
|
|
|
|
src := "example.com/test-versions/name/provider"
|
|
|
|
modsrc, err := regsrc.ParseModuleSource(src)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2021-11-01 21:09:16 +01:00
|
|
|
resp, err := client.ModuleVersions(context.Background(), modsrc)
|
2020-03-19 15:20:10 +01:00
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected request to fail", err)
|
|
|
|
}
|
|
|
|
if resp != nil {
|
|
|
|
t.Fatal("unexpected response", *resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify maxRetryErrorHandler handler returned the error
|
|
|
|
if !strings.Contains(err.Error(), "the request failed, please try again later") {
|
|
|
|
t.Fatal("unexpected error, got:", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLookupModuleNetworkError(t *testing.T) {
|
|
|
|
server := test.RegistryRetryableErrorsServer()
|
|
|
|
client := NewClient(test.Disco(server), nil)
|
|
|
|
|
|
|
|
// Shut down the server to simulate network failure
|
|
|
|
server.Close()
|
|
|
|
|
|
|
|
src := "example.com/test-versions/name/provider"
|
|
|
|
modsrc, err := regsrc.ParseModuleSource(src)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2021-11-01 21:09:16 +01:00
|
|
|
resp, err := client.ModuleVersions(context.Background(), modsrc)
|
2020-03-19 15:20:10 +01:00
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected request to fail", err)
|
|
|
|
}
|
|
|
|
if resp != nil {
|
|
|
|
t.Fatal("unexpected response", *resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify maxRetryErrorHandler handler returned the correct error
|
|
|
|
if !strings.Contains(err.Error(), "the request failed after 2 attempts, please try again later") {
|
|
|
|
t.Fatal("unexpected error, got:", err)
|
|
|
|
}
|
|
|
|
}
|