Files
opentf/internal/getproviders/registry_source_test.go
2025-10-13 10:00:19 +03:00

248 lines
7.7 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 getproviders
import (
"context"
"fmt"
"regexp"
"strings"
"testing"
"github.com/apparentlymart/go-versions/versions"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-retryablehttp"
regaddr "github.com/opentofu/registry-address/v2"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/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 OpenTofu provider registry`,
},
{
"too-new.example.com/foo/bar",
nil,
`host too-new.example.com does not support the provider registry protocol required by this OpenTofu version, but may be compatible with a different OpenTofu version`,
},
{
"fails.example.com/foo/bar",
nil,
`could not query provider registry for fails.example.com/foo/bar: request failed after 2 attempts: 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: <nil>", 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: <nil>\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 := testRegistrySourceWithLocationConfig(t, LocationConfig{ProviderDownloadRetries: 3})
defer close()
validMeta := 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{URL: baseURL + "/pkg/awesomesauce/happycloud_1.2.0.zip", ClientBuilder: func(ctx context.Context) *retryablehttp.Client {
return packageHTTPUrlClientWithRetry(ctx, source.locationConfig.ProviderDownloadRetries)
}},
}
validMeta.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(
validMeta,
[]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n000000000000000000000000000000000000000000000000000000000000face happycloud_1.2.0_face.zip\n"),
[]byte("GPG signature"),
[]SigningKey{
{ASCIIArmor: TestingPublicKey},
},
regaddr.Provider{Hostname: "example.com", Namespace: "awesomesauce", Type: "happycloud"},
),
)
tests := []struct {
provider string
version string
os, arch string
want PackageMeta
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",
validMeta,
``,
},
{
"example.com/awesomesauce/happycloud",
"1.2.0",
"nonexist", "amd64",
PackageMeta{},
`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{},
`host not.example.com does not offer a OpenTofu provider registry`,
},
{
"too-new.example.com/awesomesauce/happycloud",
"1.2.0",
"linux", "amd64",
PackageMeta{},
`host too-new.example.com does not support the provider registry protocol required by this OpenTofu version, but may be compatible with a different OpenTofu version`,
},
{
"fails.example.com/awesomesauce/happycloud",
"1.2.0",
"linux", "amd64",
PackageMeta{},
`could not query provider registry for fails.example.com/awesomesauce/happycloud: request failed after 2 attempts: 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.Option{cmp.Comparer(Version.Same), cmp.Comparer(func(a, b func(ctx context.Context) *retryablehttp.Client) bool {
given := a(t.Context())
got := b(t.Context())
if got.RetryMax != given.RetryMax {
t.Logf("expected to have retry max as %d but got %d", got.RetryMax, given.RetryMax)
}
return true
})}
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: <nil>", 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: <nil>\nwant: %s", test.wantErr)
}
if diff := cmp.Diff(got, test.want, cmpOpts...); diff != "" {
t.Errorf("wrong result\n%s", diff)
}
})
}
}