blob: b9f7954e39d77ac7e52bf002b9244abe32b1beb9 [file] [log] [blame] [edit]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package rpcapi
import (
"context"
"encoding/json"
"fmt"
"os"
"path"
"strings"
"testing"
"github.com/apparentlymart/go-versions/versions"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/internal/rpcapi/terraform1"
"github.com/hashicorp/terraform/internal/rpcapi/terraform1/packages"
)
func TestPackagesServer_ProviderPackageVersions(t *testing.T) {
tcs := map[string]struct {
source string
expectedVersions []string
expectedWarnings []string
sourceFn providerSourceFn
}{
"single_version": {
source: "hashicorp/foo",
expectedVersions: []string{"0.1.0"},
sourceFn: func(_ *disco.Disco) getproviders.Source {
packages := []getproviders.PackageMeta{
{
Provider: addrs.MustParseProviderSourceString("hashicorp/foo"),
Version: versions.MustParseVersion("0.1.0"),
},
}
return getproviders.NewMockSource(packages, nil)
},
},
"multiple_versions": {
source: "hashicorp/foo",
expectedVersions: []string{"0.1.0", "0.2.0"},
sourceFn: func(_ *disco.Disco) getproviders.Source {
packages := []getproviders.PackageMeta{
{
Provider: addrs.MustParseProviderSourceString("hashicorp/foo"),
Version: versions.MustParseVersion("0.1.0"),
},
{
Provider: addrs.MustParseProviderSourceString("hashicorp/foo"),
Version: versions.MustParseVersion("0.2.0"),
},
}
return getproviders.NewMockSource(packages, nil)
},
},
"with_warnings": {
source: "hashicorp/foo",
expectedVersions: []string{"0.1.0"},
expectedWarnings: []string{"- warning one", "- warning two"},
sourceFn: func(_ *disco.Disco) getproviders.Source {
packages := []getproviders.PackageMeta{
{
Provider: addrs.MustParseProviderSourceString("hashicorp/foo"),
Version: versions.MustParseVersion("0.1.0"),
},
}
warnings := map[addrs.Provider]getproviders.Warnings{
addrs.MustParseProviderSourceString("hashicorp/foo"): {
"warning one",
"warning two",
},
}
return getproviders.NewMockSource(packages, warnings)
},
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
service := &packagesServer{
providerSourceFn: tc.sourceFn,
}
response, err := service.ProviderPackageVersions(context.Background(), &packages.ProviderPackageVersions_Request{
SourceAddr: tc.source,
})
if err != nil {
t.Fatal(err)
}
if len(tc.expectedWarnings) > 0 {
for _, diag := range response.Diagnostics {
if diag.Severity == terraform1.Diagnostic_WARNING && diag.Summary == "Additional provider information from registry" {
expected := fmt.Sprintf("The remote registry returned warnings for %s:\n%s", tc.source, strings.Join(tc.expectedWarnings, "\n"))
if diff := cmp.Diff(expected, diag.Detail); len(diff) > 0 {
t.Error(diff)
}
}
}
// We're expecting only one diagnostic with the warnings.
if len(response.Diagnostics) > 1 {
for _, diag := range response.Diagnostics {
t.Errorf("unexpected diagnostics: %s", diag.Detail)
}
return
}
} else {
// Otherwise we're expecting no diagnostics.
if len(response.Diagnostics) > 0 {
for _, diag := range response.Diagnostics {
t.Errorf("unexpected diagnostics: %s", diag.Detail)
}
return
}
}
if diff := cmp.Diff(tc.expectedVersions, response.Versions); len(diff) > 0 {
t.Error(diff)
}
})
}
}
func TestPackagesServer_FetchProviderPackage(t *testing.T) {
providerHashes := providerHashes(t)
tcs := map[string]struct {
// source, version, platforms, and hashes are what we're going to pass
// in as the request.
source string
version string
platforms []string
hashes []string
// platformLocations, and platformHashes are what we're going to use to
// create our virtual provider metadata.
platformLocations map[string]string
platformHashes map[string][]string
// diagnostics are the expected diagnostics for each platform.
diagnostics map[string][]string
}{
"single_version_and_platform": {
source: "hashicorp/foo",
version: "0.1.0",
platforms: []string{"linux_amd64"},
platformLocations: map[string]string{
"linux_amd64": "terraform_provider_foo",
},
},
"single_version_multiple_platforms": {
source: "hashicorp/foo",
version: "0.1.0",
platforms: []string{"linux_amd64", "darwin_arm64"},
platformLocations: map[string]string{
"linux_amd64": "terraform_provider_foo",
"darwin_arm64": "terraform_provider_bar",
},
},
"single_version_and_platform_with_hashes": {
source: "hashicorp/foo",
version: "0.1.0",
platforms: []string{"linux_amd64"},
platformLocations: map[string]string{
"linux_amd64": "terraform_provider_foo",
},
platformHashes: map[string][]string{
"linux_amd64": {
"h1:dJTExJ11p+lRE8FAm4HWzTw+uMEyfE6AXXxiOgl/nB0=",
},
},
},
"single_version_and_platform_with_hashes_clash": {
source: "hashicorp/foo",
version: "0.1.0",
hashes: []string{"h1:Hod4iOH+qbXMtH4orEmCem6F3T+YRPhDSNlXmOIRNuY="},
platforms: []string{"linux_amd64"},
platformLocations: map[string]string{
"linux_amd64": "terraform_provider_foo",
},
platformHashes: map[string][]string{
"linux_amd64": {
"h1:dJTExJ11p+lRE8FAm4HWzTw+uMEyfE6AXXxiOgl/nB0=",
},
},
diagnostics: map[string][]string{
"linux_amd64": {
"the local package for registry.terraform.io/hashicorp/foo 0.1.0 doesn't match any of the checksums previously recorded in the dependency lock file",
},
},
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
service := &packagesServer{
providerSourceFn: func(_ *disco.Disco) getproviders.Source {
var providers []getproviders.PackageMeta
for _, p := range tc.platforms {
platform := parsePlatform(t, p)
var authentication getproviders.PackageAuthentication
if len(tc.platformHashes) > 0 {
authentication = getproviders.NewPackageHashAuthentication(platform, func() []getproviders.Hash {
var hashes []getproviders.Hash
for _, hash := range tc.platformHashes[p] {
hashes = append(hashes, getproviders.Hash(hash))
}
return hashes
}())
}
providers = append(providers, getproviders.PackageMeta{
Provider: addrs.MustParseProviderSourceString(tc.source),
Version: versions.MustParseVersion(tc.version),
TargetPlatform: platform,
Location: getproviders.PackageLocalDir(path.Join("testdata", "providers", tc.platformLocations[p])),
Authentication: authentication,
})
}
return getproviders.NewMockSource(providers, nil)
},
}
cacheDir := t.TempDir()
response, err := service.FetchProviderPackage(context.Background(), &packages.FetchProviderPackage_Request{
CacheDir: cacheDir,
SourceAddr: tc.source,
Version: tc.version,
Platforms: tc.platforms,
Hashes: tc.hashes,
})
if err != nil {
t.Fatal(err)
}
if len(response.Diagnostics) > 0 {
for _, diag := range response.Diagnostics {
t.Errorf("unexpected diagnostics: %s", diag.Detail)
}
return
}
if len(response.Results) != len(tc.platforms) {
t.Fatalf("wrong number of results")
}
for ix, platform := range tc.platforms {
result := response.Results[ix]
if tc.diagnostics != nil && len(tc.diagnostics[platform]) > 0 {
if len(result.Diagnostics) != len(tc.diagnostics[platform]) {
t.Fatalf("expected %d diagnostics for %s but found %d", len(tc.diagnostics[platform]), platform, len(result.Diagnostics))
}
for ix, expected := range tc.diagnostics[platform] {
if !strings.Contains(result.Diagnostics[ix].Detail, expected) {
t.Errorf("expected: %s\nactual: %s", expected, result.Diagnostics[ix])
}
}
return
} else {
if len(result.Diagnostics) > 0 {
for _, diag := range result.Diagnostics {
t.Errorf("unexpected diagnostics for %s: %s", platform, diag.Detail)
}
return
}
}
if diff := cmp.Diff(providerHashes[tc.platformLocations[platform]], result.Provider.Hashes); len(diff) > 0 {
t.Error(diff)
}
}
})
}
}
func providerHashes(t *testing.T) map[string][]string {
var hashes map[string][]string
data, err := os.ReadFile("testdata/providers/hashes.json")
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(data, &hashes); err != nil {
t.Fatal(err)
}
return hashes
}
func parsePlatform(t *testing.T, raw string) getproviders.Platform {
platform, err := getproviders.ParsePlatform(raw)
if err != nil {
t.Fatal(err)
}
return platform
}