| package getproviders |
| |
| import ( |
| "context" |
| "fmt" |
| "net/http" |
| "net/http/httptest" |
| "net/url" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| svchost "github.com/hashicorp/terraform-svchost" |
| svcauth "github.com/hashicorp/terraform-svchost/auth" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| ) |
| |
| func TestHTTPMirrorSource(t *testing.T) { |
| // For mirrors we require a HTTPS server, so we'll use httptest to create |
| // one. However, that means we need to instantiate the source in an unusual |
| // way to force it to use the test client that is configured to trust the |
| // test server. |
| httpServer := httptest.NewTLSServer(http.HandlerFunc(testHTTPMirrorSourceHandler)) |
| defer httpServer.Close() |
| httpClient := httpServer.Client() |
| baseURL, err := url.Parse(httpServer.URL) |
| if err != nil { |
| t.Fatalf("httptest.NewTLSServer returned a server with an invalid URL") |
| } |
| creds := svcauth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{ |
| svchost.Hostname(baseURL.Host): { |
| "token": "placeholder-token", |
| }, |
| }) |
| source := newHTTPMirrorSourceWithHTTPClient(baseURL, creds, httpClient) |
| |
| existingProvider := addrs.MustParseProviderSourceString("terraform.io/test/exists") |
| missingProvider := addrs.MustParseProviderSourceString("terraform.io/test/missing") |
| failingProvider := addrs.MustParseProviderSourceString("terraform.io/test/fails") |
| redirectingProvider := addrs.MustParseProviderSourceString("terraform.io/test/redirects") |
| redirectLoopProvider := addrs.MustParseProviderSourceString("terraform.io/test/redirect-loop") |
| tosPlatform := Platform{OS: "tos", Arch: "m68k"} |
| |
| t.Run("AvailableVersions for provider that exists", func(t *testing.T) { |
| got, _, err := source.AvailableVersions(context.Background(), existingProvider) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| want := VersionList{ |
| MustParseVersion("1.0.0"), |
| MustParseVersion("1.0.1"), |
| MustParseVersion("1.0.2-beta.1"), |
| } |
| if diff := cmp.Diff(want, got); diff != "" { |
| t.Errorf("wrong result\n%s", diff) |
| } |
| }) |
| t.Run("AvailableVersions for provider that doesn't exist", func(t *testing.T) { |
| _, _, err := source.AvailableVersions(context.Background(), missingProvider) |
| switch err := err.(type) { |
| case ErrProviderNotFound: |
| if got, want := err.Provider, missingProvider; got != want { |
| t.Errorf("wrong provider in error\ngot: %s\nwant: %s", got, want) |
| } |
| default: |
| t.Fatalf("wrong error type %T; want ErrProviderNotFound", err) |
| } |
| }) |
| t.Run("AvailableVersions without required credentials", func(t *testing.T) { |
| unauthSource := newHTTPMirrorSourceWithHTTPClient(baseURL, nil, httpClient) |
| _, _, err := unauthSource.AvailableVersions(context.Background(), existingProvider) |
| switch err := err.(type) { |
| case ErrUnauthorized: |
| if got, want := string(err.Hostname), baseURL.Host; got != want { |
| t.Errorf("wrong hostname in error\ngot: %s\nwant: %s", got, want) |
| } |
| default: |
| t.Fatalf("wrong error type %T; want ErrUnauthorized", err) |
| } |
| }) |
| t.Run("AvailableVersions when the response is a server error", func(t *testing.T) { |
| _, _, err := source.AvailableVersions(context.Background(), failingProvider) |
| switch err := err.(type) { |
| case ErrQueryFailed: |
| if got, want := err.Provider, failingProvider; got != want { |
| t.Errorf("wrong provider in error\ngot: %s\nwant: %s", got, want) |
| } |
| if err.MirrorURL != source.baseURL { |
| t.Errorf("error does not refer to the mirror URL") |
| } |
| default: |
| t.Fatalf("wrong error type %T; want ErrQueryFailed", err) |
| } |
| }) |
| t.Run("AvailableVersions for provider that redirects", func(t *testing.T) { |
| got, _, err := source.AvailableVersions(context.Background(), redirectingProvider) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| want := VersionList{ |
| MustParseVersion("1.0.0"), |
| } |
| if diff := cmp.Diff(want, got); diff != "" { |
| t.Errorf("wrong result\n%s", diff) |
| } |
| }) |
| t.Run("AvailableVersions for provider that redirects too much", func(t *testing.T) { |
| _, _, err := source.AvailableVersions(context.Background(), redirectLoopProvider) |
| if err == nil { |
| t.Fatalf("succeeded; expected error") |
| } |
| }) |
| t.Run("PackageMeta for a version that exists and has a hash", func(t *testing.T) { |
| version := MustParseVersion("1.0.0") |
| got, err := source.PackageMeta(context.Background(), existingProvider, version, tosPlatform) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| want := PackageMeta{ |
| Provider: existingProvider, |
| Version: version, |
| TargetPlatform: tosPlatform, |
| Filename: "terraform-provider-test_v1.0.0_tos_m68k.zip", |
| Location: PackageHTTPURL(httpServer.URL + "/terraform.io/test/exists/terraform-provider-test_v1.0.0_tos_m68k.zip"), |
| Authentication: packageHashAuthentication{ |
| RequiredHashes: []Hash{"h1:placeholder-hash"}, |
| AllHashes: []Hash{"h1:placeholder-hash", "h0:unacceptable-hash"}, |
| Platform: Platform{"tos", "m68k"}, |
| }, |
| } |
| if diff := cmp.Diff(want, got); diff != "" { |
| t.Errorf("wrong result\n%s", diff) |
| } |
| |
| gotHashes := got.AcceptableHashes() |
| wantHashes := []Hash{"h1:placeholder-hash", "h0:unacceptable-hash"} |
| if diff := cmp.Diff(wantHashes, gotHashes); diff != "" { |
| t.Errorf("wrong acceptable hashes\n%s", diff) |
| } |
| }) |
| t.Run("PackageMeta for a version that exists and has no hash", func(t *testing.T) { |
| version := MustParseVersion("1.0.1") |
| got, err := source.PackageMeta(context.Background(), existingProvider, version, tosPlatform) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| want := PackageMeta{ |
| Provider: existingProvider, |
| Version: version, |
| TargetPlatform: tosPlatform, |
| Filename: "terraform-provider-test_v1.0.1_tos_m68k.zip", |
| Location: PackageHTTPURL(httpServer.URL + "/terraform.io/test/exists/terraform-provider-test_v1.0.1_tos_m68k.zip"), |
| Authentication: nil, |
| } |
| if diff := cmp.Diff(want, got); diff != "" { |
| t.Errorf("wrong result\n%s", diff) |
| } |
| }) |
| t.Run("PackageMeta for a version that exists but has no archives", func(t *testing.T) { |
| version := MustParseVersion("1.0.2-beta.1") |
| _, err := source.PackageMeta(context.Background(), existingProvider, version, tosPlatform) |
| switch err := err.(type) { |
| case ErrPlatformNotSupported: |
| if got, want := err.Provider, existingProvider; got != want { |
| t.Errorf("wrong provider in error\ngot: %s\nwant: %s", got, want) |
| } |
| if got, want := err.Platform, tosPlatform; got != want { |
| t.Errorf("wrong platform in error\ngot: %s\nwant: %s", got, want) |
| } |
| if err.MirrorURL != source.baseURL { |
| t.Errorf("error does not contain the mirror URL") |
| } |
| default: |
| t.Fatalf("wrong error type %T; want ErrPlatformNotSupported", err) |
| } |
| }) |
| t.Run("PackageMeta with redirect to a version that exists", func(t *testing.T) { |
| version := MustParseVersion("1.0.0") |
| got, err := source.PackageMeta(context.Background(), redirectingProvider, version, tosPlatform) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| want := PackageMeta{ |
| Provider: redirectingProvider, |
| Version: version, |
| TargetPlatform: tosPlatform, |
| Filename: "terraform-provider-test.zip", |
| |
| // NOTE: The final URL is interpreted relative to the redirect |
| // target, not relative to what we originally requested. |
| Location: PackageHTTPURL(httpServer.URL + "/redirect-target/terraform-provider-test.zip"), |
| } |
| if diff := cmp.Diff(want, got); diff != "" { |
| t.Errorf("wrong result\n%s", diff) |
| } |
| }) |
| t.Run("PackageMeta when the response is a server error", func(t *testing.T) { |
| version := MustParseVersion("1.0.0") |
| _, err := source.PackageMeta(context.Background(), failingProvider, version, tosPlatform) |
| switch err := err.(type) { |
| case ErrQueryFailed: |
| if got, want := err.Provider, failingProvider; got != want { |
| t.Errorf("wrong provider in error\ngot: %s\nwant: %s", got, want) |
| } |
| if err.MirrorURL != source.baseURL { |
| t.Errorf("error does not contain the mirror URL") |
| } |
| default: |
| t.Fatalf("wrong error type %T; want ErrQueryFailed", err) |
| } |
| }) |
| } |
| |
| func testHTTPMirrorSourceHandler(resp http.ResponseWriter, req *http.Request) { |
| if auth := req.Header.Get("authorization"); auth != "Bearer placeholder-token" { |
| resp.WriteHeader(401) |
| fmt.Fprintln(resp, "incorrect auth token") |
| } |
| |
| switch req.URL.Path { |
| case "/terraform.io/test/exists/index.json": |
| resp.Header().Add("Content-Type", "application/json; ignored=yes") |
| resp.WriteHeader(200) |
| fmt.Fprint(resp, ` |
| { |
| "versions": { |
| "1.0.0": {}, |
| "1.0.1": {}, |
| "1.0.2-beta.1": {} |
| } |
| } |
| `) |
| |
| case "/terraform.io/test/fails/index.json", "/terraform.io/test/fails/1.0.0.json": |
| resp.WriteHeader(500) |
| fmt.Fprint(resp, "server error") |
| |
| case "/terraform.io/test/exists/1.0.0.json": |
| resp.Header().Add("Content-Type", "application/json; ignored=yes") |
| resp.WriteHeader(200) |
| fmt.Fprint(resp, ` |
| { |
| "archives": { |
| "tos_m68k": { |
| "url": "terraform-provider-test_v1.0.0_tos_m68k.zip", |
| "hashes": [ |
| "h1:placeholder-hash", |
| "h0:unacceptable-hash" |
| ] |
| } |
| } |
| } |
| `) |
| |
| case "/terraform.io/test/exists/1.0.1.json": |
| resp.Header().Add("Content-Type", "application/json; ignored=yes") |
| resp.WriteHeader(200) |
| fmt.Fprint(resp, ` |
| { |
| "archives": { |
| "tos_m68k": { |
| "url": "terraform-provider-test_v1.0.1_tos_m68k.zip" |
| } |
| } |
| } |
| `) |
| |
| case "/terraform.io/test/exists/1.0.2-beta.1.json": |
| resp.Header().Add("Content-Type", "application/json; ignored=yes") |
| resp.WriteHeader(200) |
| fmt.Fprint(resp, ` |
| { |
| "archives": {} |
| } |
| `) |
| |
| case "/terraform.io/test/redirects/index.json": |
| resp.Header().Add("location", "/redirect-target/index.json") |
| resp.WriteHeader(301) |
| fmt.Fprint(resp, "redirect") |
| |
| case "/redirect-target/index.json": |
| resp.Header().Add("Content-Type", "application/json") |
| resp.WriteHeader(200) |
| fmt.Fprint(resp, ` |
| { |
| "versions": { |
| "1.0.0": {} |
| } |
| } |
| `) |
| |
| case "/terraform.io/test/redirects/1.0.0.json": |
| resp.Header().Add("location", "/redirect-target/1.0.0.json") |
| resp.WriteHeader(301) |
| fmt.Fprint(resp, "redirect") |
| |
| case "/redirect-target/1.0.0.json": |
| resp.Header().Add("Content-Type", "application/json") |
| resp.WriteHeader(200) |
| fmt.Fprint(resp, ` |
| { |
| "archives": { |
| "tos_m68k": { |
| "url": "terraform-provider-test.zip" |
| } |
| } |
| } |
| `) |
| |
| case "/terraform.io/test/redirect-loop/index.json": |
| // This is intentionally redirecting to itself, to create a loop. |
| resp.Header().Add("location", req.URL.Path) |
| resp.WriteHeader(301) |
| fmt.Fprint(resp, "redirect loop") |
| |
| default: |
| resp.WriteHeader(404) |
| fmt.Fprintln(resp, "not found") |
| } |
| } |