package getproviders import ( "context" "fmt" "regexp" "strings" "testing" "github.com/apparentlymart/go-versions/versions" "github.com/google/go-cmp/cmp" svchost "github.com/hashicorp/terraform-svchost" "github.com/hashicorp/terraform/internal/addrs" ) func TestSourceAvailableVersions(t *testing.T) { source, baseURL, close := testRegistrySource(t) defer close() tests := []struct { provider string wantVersions []string wantErr string }{ // These test cases are relying on behaviors of the fake provider // registry server implemented in registry_client_test.go. { "example.com/awesomesauce/happycloud", []string{"0.1.0", "1.0.0", "1.2.0", "2.0.0"}, ``, }, { "example.com/weaksauce/no-versions", nil, ``, // having no versions is not an error, it's just odd }, { "example.com/nonexist/nonexist", nil, `provider registry example.com does not have a provider named example.com/nonexist/nonexist`, }, { "not.example.com/foo/bar", nil, `host not.example.com does not offer a Terraform provider registry`, }, { "too-new.example.com/foo/bar", nil, `host too-new.example.com does not support the provider registry protocol required by this Terraform version, but may be compatible with a different Terraform version`, }, { "fails.example.com/foo/bar", nil, `could not query provider registry for fails.example.com/foo/bar: the request failed after 2 attempts, please try again later: Get "` + baseURL + `/fails-immediately/foo/bar/versions": EOF`, }, } for _, test := range tests { t.Run(test.provider, func(t *testing.T) { provider := addrs.MustParseProviderSourceString(test.provider) gotVersions, _, err := source.AvailableVersions(context.Background(), provider) if err != nil { if test.wantErr == "" { t.Fatalf("wrong error\ngot: %s\nwant: ", err.Error()) } if got, want := err.Error(), test.wantErr; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } return } if test.wantErr != "" { t.Fatalf("wrong error\ngot: \nwant: %s", test.wantErr) } var gotVersionsStr []string if gotVersions != nil { gotVersionsStr = make([]string, len(gotVersions)) for i, v := range gotVersions { gotVersionsStr[i] = v.String() } } if diff := cmp.Diff(test.wantVersions, gotVersionsStr); diff != "" { t.Errorf("wrong result\n%s", diff) } }) } } func TestSourceAvailableVersions_warnings(t *testing.T) { source, _, close := testRegistrySource(t) defer close() provider := addrs.MustParseProviderSourceString("example.com/weaksauce/no-versions") _, warnings, err := source.AvailableVersions(context.Background(), provider) if err != nil { t.Fatalf("unexpected error: %s", err.Error()) } if len(warnings) != 1 { t.Fatalf("wrong number of warnings. Expected 1, got %d", len(warnings)) } } func TestSourcePackageMeta(t *testing.T) { source, baseURL, close := testRegistrySource(t) defer close() tests := []struct { provider string version string os, arch string want PackageMeta wantHashes []Hash wantErr string }{ // These test cases are relying on behaviors of the fake provider // registry server implemented in registry_client_test.go. { "example.com/awesomesauce/happycloud", "1.2.0", "linux", "amd64", PackageMeta{ Provider: addrs.NewProvider( svchost.Hostname("example.com"), "awesomesauce", "happycloud", ), Version: versions.MustParseVersion("1.2.0"), ProtocolVersions: VersionList{versions.MustParseVersion("5.0.0")}, TargetPlatform: Platform{"linux", "amd64"}, Filename: "happycloud_1.2.0.zip", Location: PackageHTTPURL(baseURL + "/pkg/awesomesauce/happycloud_1.2.0.zip"), Authentication: PackageAuthenticationAll( NewMatchingChecksumAuthentication( []byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n000000000000000000000000000000000000000000000000000000000000face happycloud_1.2.0_face.zip\n"), "happycloud_1.2.0.zip", [32]byte{30: 0xf0, 31: 0x0d}, ), NewArchiveChecksumAuthentication(Platform{"linux", "amd64"}, [32]byte{30: 0xf0, 31: 0x0d}), NewSignatureAuthentication( []byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n000000000000000000000000000000000000000000000000000000000000face happycloud_1.2.0_face.zip\n"), []byte("GPG signature"), []SigningKey{ {ASCIIArmor: HashicorpPublicKey}, }, ), ), }, []Hash{ "zh:000000000000000000000000000000000000000000000000000000000000f00d", "zh:000000000000000000000000000000000000000000000000000000000000face", }, ``, }, { "example.com/awesomesauce/happycloud", "1.2.0", "nonexist", "amd64", PackageMeta{}, nil, `provider example.com/awesomesauce/happycloud 1.2.0 is not available for nonexist_amd64`, }, { "not.example.com/awesomesauce/happycloud", "1.2.0", "linux", "amd64", PackageMeta{}, nil, `host not.example.com does not offer a Terraform provider registry`, }, { "too-new.example.com/awesomesauce/happycloud", "1.2.0", "linux", "amd64", PackageMeta{}, nil, `host too-new.example.com does not support the provider registry protocol required by this Terraform version, but may be compatible with a different Terraform version`, }, { "fails.example.com/awesomesauce/happycloud", "1.2.0", "linux", "amd64", PackageMeta{}, nil, `could not query provider registry for fails.example.com/awesomesauce/happycloud: the request failed after 2 attempts, please try again later: Get "http://placeholder-origin/fails-immediately/awesomesauce/happycloud/1.2.0/download/linux/amd64": EOF`, }, } // Sometimes error messages contain specific HTTP endpoint URLs, but // since our test server is on a random port we'd not be able to // consistently match those. Instead, we'll normalize the URLs. urlPattern := regexp.MustCompile(`http://[^/]+/`) cmpOpts := cmp.Comparer(Version.Same) for _, test := range tests { t.Run(fmt.Sprintf("%s for %s_%s", test.provider, test.os, test.arch), func(t *testing.T) { // TEMP: We don't yet have a function for parsing provider // source addresses so we'll just fake it in here for now. parts := strings.Split(test.provider, "/") providerAddr := addrs.Provider{ Hostname: svchost.Hostname(parts[0]), Namespace: parts[1], Type: parts[2], } version := versions.MustParseVersion(test.version) got, err := source.PackageMeta(context.Background(), providerAddr, version, Platform{test.os, test.arch}) if err != nil { if test.wantErr == "" { t.Fatalf("wrong error\ngot: %s\nwant: ", err.Error()) } gotErr := urlPattern.ReplaceAllLiteralString(err.Error(), "http://placeholder-origin/") if got, want := gotErr, test.wantErr; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } return } if test.wantErr != "" { t.Fatalf("wrong error\ngot: \nwant: %s", test.wantErr) } if diff := cmp.Diff(test.want, got, cmpOpts); diff != "" { t.Errorf("wrong result\n%s", diff) } if diff := cmp.Diff(test.wantHashes, got.AcceptableHashes()); diff != "" { t.Errorf("wrong AcceptableHashes result\n%s", diff) } }) } }