Merge pull request #25504 from hashicorp/alisdair/post-install-cache-validate
Add post-install provider cache validation and error reporting
This commit is contained in:
commit
53e587e1a6
|
@ -653,6 +653,18 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State,
|
||||||
"https://www.terraform.io/docs/plugins/signing.html"))
|
"https://www.terraform.io/docs/plugins/signing.html"))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
HashPackageFailure: func(provider addrs.Provider, version getproviders.Version, err error) {
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Failed to validate installed provider",
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Validating provider %s v%s failed: %s",
|
||||||
|
provider.ForDisplay(),
|
||||||
|
version,
|
||||||
|
err,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
mode := providercache.InstallNewProvidersOnly
|
mode := providercache.InstallNewProvidersOnly
|
||||||
|
|
|
@ -951,6 +951,67 @@ func TestInit_getProviderSource(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInit_getProviderInvalidPackage(t *testing.T) {
|
||||||
|
// Create a temporary working directory that is empty
|
||||||
|
td := tempDir(t)
|
||||||
|
copy.CopyDir(testFixturePath("init-get-provider-invalid-package"), td)
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
overrides := metaOverridesForProvider(testProvider())
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
|
||||||
|
// create a provider source which allows installing an invalid package
|
||||||
|
addr := addrs.MustParseProviderSourceString("invalid/package")
|
||||||
|
version := getproviders.MustParseVersion("1.0.0")
|
||||||
|
meta, close, err := getproviders.FakeInstallablePackageMeta(
|
||||||
|
addr,
|
||||||
|
version,
|
||||||
|
getproviders.VersionList{getproviders.MustParseVersion("5.0")},
|
||||||
|
getproviders.CurrentPlatform,
|
||||||
|
"terraform-package", // should be "terraform-provider-package"
|
||||||
|
)
|
||||||
|
defer close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to prepare fake package for %s %s: %s", addr.ForDisplay(), version, err)
|
||||||
|
}
|
||||||
|
providerSource := getproviders.NewMockSource([]getproviders.PackageMeta{meta}, nil)
|
||||||
|
|
||||||
|
m := Meta{
|
||||||
|
testingOverrides: overrides,
|
||||||
|
Ui: ui,
|
||||||
|
ProviderSource: providerSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &InitCommand{
|
||||||
|
Meta: m,
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-backend=false", // should be possible to install plugins without backend init
|
||||||
|
}
|
||||||
|
if code := c.Run(args); code != 1 {
|
||||||
|
t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid provider should be installed
|
||||||
|
packagePath := fmt.Sprintf(".terraform/plugins/registry.terraform.io/invalid/package/1.0.0/%s/terraform-package", getproviders.CurrentPlatform)
|
||||||
|
if _, err := os.Stat(packagePath); os.IsNotExist(err) {
|
||||||
|
t.Fatal("provider 'invalid/package' not downloaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
wantErrors := []string{
|
||||||
|
"Failed to validate installed provider",
|
||||||
|
"could not find executable file starting with terraform-provider-package",
|
||||||
|
}
|
||||||
|
got := ui.ErrorWriter.String()
|
||||||
|
for _, wantError := range wantErrors {
|
||||||
|
if !strings.Contains(got, wantError) {
|
||||||
|
t.Fatalf("missing error:\nwant: %q\n got: %q", wantError, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestInit_getProviderDetectedLegacy(t *testing.T) {
|
func TestInit_getProviderDetectedLegacy(t *testing.T) {
|
||||||
// Create a temporary working directory that is empty
|
// Create a temporary working directory that is empty
|
||||||
td := tempDir(t)
|
td := tempDir(t)
|
||||||
|
@ -1063,7 +1124,6 @@ func TestInit_providerSource(t *testing.T) {
|
||||||
Provider: addrs.NewDefaultProvider("test"),
|
Provider: addrs.NewDefaultProvider("test"),
|
||||||
Version: getproviders.MustParseVersion("1.2.3"),
|
Version: getproviders.MustParseVersion("1.2.3"),
|
||||||
PackageDir: expectedPackageInstallPath("test", "1.2.3", false),
|
PackageDir: expectedPackageInstallPath("test", "1.2.3", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("test", "1.2.3", true),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
addrs.NewDefaultProvider("test-beta"): {
|
addrs.NewDefaultProvider("test-beta"): {
|
||||||
|
@ -1071,7 +1131,6 @@ func TestInit_providerSource(t *testing.T) {
|
||||||
Provider: addrs.NewDefaultProvider("test-beta"),
|
Provider: addrs.NewDefaultProvider("test-beta"),
|
||||||
Version: getproviders.MustParseVersion("1.2.4"),
|
Version: getproviders.MustParseVersion("1.2.4"),
|
||||||
PackageDir: expectedPackageInstallPath("test-beta", "1.2.4", false),
|
PackageDir: expectedPackageInstallPath("test-beta", "1.2.4", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("test-beta", "1.2.4", true),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
addrs.NewDefaultProvider("source"): {
|
addrs.NewDefaultProvider("source"): {
|
||||||
|
@ -1079,7 +1138,6 @@ func TestInit_providerSource(t *testing.T) {
|
||||||
Provider: addrs.NewDefaultProvider("source"),
|
Provider: addrs.NewDefaultProvider("source"),
|
||||||
Version: getproviders.MustParseVersion("1.2.3"),
|
Version: getproviders.MustParseVersion("1.2.3"),
|
||||||
PackageDir: expectedPackageInstallPath("source", "1.2.3", false),
|
PackageDir: expectedPackageInstallPath("source", "1.2.3", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("source", "1.2.3", true),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1097,19 +1155,16 @@ func TestInit_providerSource(t *testing.T) {
|
||||||
Provider: addrs.NewDefaultProvider("test-beta"),
|
Provider: addrs.NewDefaultProvider("test-beta"),
|
||||||
Version: getproviders.MustParseVersion("1.2.4"),
|
Version: getproviders.MustParseVersion("1.2.4"),
|
||||||
PackageDir: expectedPackageInstallPath("test-beta", "1.2.4", false),
|
PackageDir: expectedPackageInstallPath("test-beta", "1.2.4", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("test-beta", "1.2.4", true),
|
|
||||||
},
|
},
|
||||||
addrs.NewDefaultProvider("test"): {
|
addrs.NewDefaultProvider("test"): {
|
||||||
Provider: addrs.NewDefaultProvider("test"),
|
Provider: addrs.NewDefaultProvider("test"),
|
||||||
Version: getproviders.MustParseVersion("1.2.3"),
|
Version: getproviders.MustParseVersion("1.2.3"),
|
||||||
PackageDir: expectedPackageInstallPath("test", "1.2.3", false),
|
PackageDir: expectedPackageInstallPath("test", "1.2.3", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("test", "1.2.3", true),
|
|
||||||
},
|
},
|
||||||
addrs.NewDefaultProvider("source"): {
|
addrs.NewDefaultProvider("source"): {
|
||||||
Provider: addrs.NewDefaultProvider("source"),
|
Provider: addrs.NewDefaultProvider("source"),
|
||||||
Version: getproviders.MustParseVersion("1.2.3"),
|
Version: getproviders.MustParseVersion("1.2.3"),
|
||||||
PackageDir: expectedPackageInstallPath("source", "1.2.3", false),
|
PackageDir: expectedPackageInstallPath("source", "1.2.3", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("source", "1.2.3", true),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(wantSelected, gotSelected); diff != "" {
|
if diff := cmp.Diff(wantSelected, gotSelected); diff != "" {
|
||||||
|
@ -1172,7 +1227,6 @@ func TestInit_getUpgradePlugins(t *testing.T) {
|
||||||
Provider: addrs.NewDefaultProvider("between"),
|
Provider: addrs.NewDefaultProvider("between"),
|
||||||
Version: getproviders.MustParseVersion("2.3.4"),
|
Version: getproviders.MustParseVersion("2.3.4"),
|
||||||
PackageDir: expectedPackageInstallPath("between", "2.3.4", false),
|
PackageDir: expectedPackageInstallPath("between", "2.3.4", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("between", "2.3.4", true),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// The existing version of "exact" did not match the version constraints,
|
// The existing version of "exact" did not match the version constraints,
|
||||||
|
@ -1182,14 +1236,12 @@ func TestInit_getUpgradePlugins(t *testing.T) {
|
||||||
Provider: addrs.NewDefaultProvider("exact"),
|
Provider: addrs.NewDefaultProvider("exact"),
|
||||||
Version: getproviders.MustParseVersion("1.2.3"),
|
Version: getproviders.MustParseVersion("1.2.3"),
|
||||||
PackageDir: expectedPackageInstallPath("exact", "1.2.3", false),
|
PackageDir: expectedPackageInstallPath("exact", "1.2.3", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("exact", "1.2.3", true),
|
|
||||||
},
|
},
|
||||||
// Previous version is still there, but not selected
|
// Previous version is still there, but not selected
|
||||||
{
|
{
|
||||||
Provider: addrs.NewDefaultProvider("exact"),
|
Provider: addrs.NewDefaultProvider("exact"),
|
||||||
Version: getproviders.MustParseVersion("0.0.1"),
|
Version: getproviders.MustParseVersion("0.0.1"),
|
||||||
PackageDir: expectedPackageInstallPath("exact", "0.0.1", false),
|
PackageDir: expectedPackageInstallPath("exact", "0.0.1", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("exact", "0.0.1", true),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// The existing version of "greater-than" _did_ match the constraints,
|
// The existing version of "greater-than" _did_ match the constraints,
|
||||||
|
@ -1200,14 +1252,12 @@ func TestInit_getUpgradePlugins(t *testing.T) {
|
||||||
Provider: addrs.NewDefaultProvider("greater-than"),
|
Provider: addrs.NewDefaultProvider("greater-than"),
|
||||||
Version: getproviders.MustParseVersion("2.3.4"),
|
Version: getproviders.MustParseVersion("2.3.4"),
|
||||||
PackageDir: expectedPackageInstallPath("greater-than", "2.3.4", false),
|
PackageDir: expectedPackageInstallPath("greater-than", "2.3.4", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("greater-than", "2.3.4", true),
|
|
||||||
},
|
},
|
||||||
// Previous version is still there, but not selected
|
// Previous version is still there, but not selected
|
||||||
{
|
{
|
||||||
Provider: addrs.NewDefaultProvider("greater-than"),
|
Provider: addrs.NewDefaultProvider("greater-than"),
|
||||||
Version: getproviders.MustParseVersion("2.3.3"),
|
Version: getproviders.MustParseVersion("2.3.3"),
|
||||||
PackageDir: expectedPackageInstallPath("greater-than", "2.3.3", false),
|
PackageDir: expectedPackageInstallPath("greater-than", "2.3.3", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("greater-than", "2.3.3", true),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1225,19 +1275,16 @@ func TestInit_getUpgradePlugins(t *testing.T) {
|
||||||
Provider: addrs.NewDefaultProvider("between"),
|
Provider: addrs.NewDefaultProvider("between"),
|
||||||
Version: getproviders.MustParseVersion("2.3.4"),
|
Version: getproviders.MustParseVersion("2.3.4"),
|
||||||
PackageDir: expectedPackageInstallPath("between", "2.3.4", false),
|
PackageDir: expectedPackageInstallPath("between", "2.3.4", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("between", "2.3.4", true),
|
|
||||||
},
|
},
|
||||||
addrs.NewDefaultProvider("exact"): {
|
addrs.NewDefaultProvider("exact"): {
|
||||||
Provider: addrs.NewDefaultProvider("exact"),
|
Provider: addrs.NewDefaultProvider("exact"),
|
||||||
Version: getproviders.MustParseVersion("1.2.3"),
|
Version: getproviders.MustParseVersion("1.2.3"),
|
||||||
PackageDir: expectedPackageInstallPath("exact", "1.2.3", false),
|
PackageDir: expectedPackageInstallPath("exact", "1.2.3", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("exact", "1.2.3", true),
|
|
||||||
},
|
},
|
||||||
addrs.NewDefaultProvider("greater-than"): {
|
addrs.NewDefaultProvider("greater-than"): {
|
||||||
Provider: addrs.NewDefaultProvider("greater-than"),
|
Provider: addrs.NewDefaultProvider("greater-than"),
|
||||||
Version: getproviders.MustParseVersion("2.3.4"),
|
Version: getproviders.MustParseVersion("2.3.4"),
|
||||||
PackageDir: expectedPackageInstallPath("greater-than", "2.3.4", false),
|
PackageDir: expectedPackageInstallPath("greater-than", "2.3.4", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("greater-than", "2.3.4", true),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(wantSelected, gotSelected); diff != "" {
|
if diff := cmp.Diff(wantSelected, gotSelected); diff != "" {
|
||||||
|
@ -1483,19 +1530,16 @@ func TestInit_pluginDirProviders(t *testing.T) {
|
||||||
Provider: addrs.NewDefaultProvider("between"),
|
Provider: addrs.NewDefaultProvider("between"),
|
||||||
Version: getproviders.MustParseVersion("2.3.4"),
|
Version: getproviders.MustParseVersion("2.3.4"),
|
||||||
PackageDir: expectedPackageInstallPath("between", "2.3.4", false),
|
PackageDir: expectedPackageInstallPath("between", "2.3.4", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("between", "2.3.4", true),
|
|
||||||
},
|
},
|
||||||
addrs.NewDefaultProvider("exact"): {
|
addrs.NewDefaultProvider("exact"): {
|
||||||
Provider: addrs.NewDefaultProvider("exact"),
|
Provider: addrs.NewDefaultProvider("exact"),
|
||||||
Version: getproviders.MustParseVersion("1.2.3"),
|
Version: getproviders.MustParseVersion("1.2.3"),
|
||||||
PackageDir: expectedPackageInstallPath("exact", "1.2.3", false),
|
PackageDir: expectedPackageInstallPath("exact", "1.2.3", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("exact", "1.2.3", true),
|
|
||||||
},
|
},
|
||||||
addrs.NewDefaultProvider("greater-than"): {
|
addrs.NewDefaultProvider("greater-than"): {
|
||||||
Provider: addrs.NewDefaultProvider("greater-than"),
|
Provider: addrs.NewDefaultProvider("greater-than"),
|
||||||
Version: getproviders.MustParseVersion("2.3.4"),
|
Version: getproviders.MustParseVersion("2.3.4"),
|
||||||
PackageDir: expectedPackageInstallPath("greater-than", "2.3.4", false),
|
PackageDir: expectedPackageInstallPath("greater-than", "2.3.4", false),
|
||||||
ExecutableFile: expectedPackageInstallPath("greater-than", "2.3.4", true),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(wantSelected, gotSelected); diff != "" {
|
if diff := cmp.Diff(wantSelected, gotSelected); diff != "" {
|
||||||
|
@ -1695,7 +1739,7 @@ func newMockProviderSource(t *testing.T, availableProviderVersions map[string][]
|
||||||
close()
|
close()
|
||||||
t.Fatalf("failed to parse %q as a version number for %q: %s", versionStr, addr.ForDisplay(), err)
|
t.Fatalf("failed to parse %q as a version number for %q: %s", versionStr, addr.ForDisplay(), err)
|
||||||
}
|
}
|
||||||
meta, close, err := getproviders.FakeInstallablePackageMeta(addr, version, getproviders.VersionList{getproviders.MustParseVersion("5.0")}, getproviders.CurrentPlatform)
|
meta, close, err := getproviders.FakeInstallablePackageMeta(addr, version, getproviders.VersionList{getproviders.MustParseVersion("5.0")}, getproviders.CurrentPlatform, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
close()
|
close()
|
||||||
t.Fatalf("failed to prepare fake package for %s %s: %s", addr.ForDisplay(), versionStr, err)
|
t.Fatalf("failed to prepare fake package for %s %s: %s", addr.ForDisplay(), versionStr, err)
|
||||||
|
@ -1755,7 +1799,7 @@ func installFakeProviderPackagesElsewhere(t *testing.T, cacheDir *providercache.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to parse %q as a version number for %q: %s", versionStr, name, err)
|
t.Fatalf("failed to parse %q as a version number for %q: %s", versionStr, name, err)
|
||||||
}
|
}
|
||||||
meta, close, err := getproviders.FakeInstallablePackageMeta(addr, version, getproviders.VersionList{getproviders.MustParseVersion("5.0")}, getproviders.CurrentPlatform)
|
meta, close, err := getproviders.FakeInstallablePackageMeta(addr, version, getproviders.VersionList{getproviders.MustParseVersion("5.0")}, getproviders.CurrentPlatform, "")
|
||||||
// We're going to install all these fake packages before we return,
|
// We're going to install all these fake packages before we return,
|
||||||
// so we don't need to preserve them afterwards.
|
// so we don't need to preserve them afterwards.
|
||||||
defer close()
|
defer close()
|
||||||
|
|
|
@ -209,12 +209,17 @@ func providerFactory(meta *providercache.CachedProvider) providers.Factory {
|
||||||
Output: os.Stderr,
|
Output: os.Stderr,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
execFile, err := meta.ExecutableFile()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
config := &plugin.ClientConfig{
|
config := &plugin.ClientConfig{
|
||||||
HandshakeConfig: tfplugin.Handshake,
|
HandshakeConfig: tfplugin.Handshake,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
|
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
|
||||||
Managed: true,
|
Managed: true,
|
||||||
Cmd: exec.Command(meta.ExecutableFile),
|
Cmd: exec.Command(execFile),
|
||||||
AutoMTLS: enableProviderAutoMTLS,
|
AutoMTLS: enableProviderAutoMTLS,
|
||||||
VersionedPlugins: tfplugin.VersionedPlugins,
|
VersionedPlugins: tfplugin.VersionedPlugins,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
package = {
|
||||||
|
source = "invalid/package"
|
||||||
|
version = "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -143,13 +143,15 @@ func FakePackageMeta(provider addrs.Provider, version Version, protocols Version
|
||||||
// to a temporary archive file that could actually be installed in principle.
|
// to a temporary archive file that could actually be installed in principle.
|
||||||
//
|
//
|
||||||
// Installing it will not produce a working provider though: just a fake file
|
// Installing it will not produce a working provider though: just a fake file
|
||||||
// posing as an executable.
|
// posing as an executable. The filename for the executable defaults to the
|
||||||
|
// standard terraform-provider-NAME_X.Y.Z format, but can be overridden with
|
||||||
|
// the execFilename argument.
|
||||||
//
|
//
|
||||||
// It's the caller's responsibility to call the close callback returned
|
// It's the caller's responsibility to call the close callback returned
|
||||||
// alongside the result in order to clean up the temporary file. The caller
|
// alongside the result in order to clean up the temporary file. The caller
|
||||||
// should call the callback even if this function returns an error, because
|
// should call the callback even if this function returns an error, because
|
||||||
// some error conditions leave a partially-created file on disk.
|
// some error conditions leave a partially-created file on disk.
|
||||||
func FakeInstallablePackageMeta(provider addrs.Provider, version Version, protocols VersionList, target Platform) (PackageMeta, func(), error) {
|
func FakeInstallablePackageMeta(provider addrs.Provider, version Version, protocols VersionList, target Platform, execFilename string) (PackageMeta, func(), error) {
|
||||||
f, err := ioutil.TempFile("", "terraform-getproviders-fake-package-")
|
f, err := ioutil.TempFile("", "terraform-getproviders-fake-package-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PackageMeta{}, func() {}, err
|
return PackageMeta{}, func() {}, err
|
||||||
|
@ -162,11 +164,13 @@ func FakeInstallablePackageMeta(provider addrs.Provider, version Version, protoc
|
||||||
os.Remove(f.Name())
|
os.Remove(f.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
execFilename := fmt.Sprintf("terraform-provider-%s_%s", provider.Type, version.String())
|
if execFilename == "" {
|
||||||
|
execFilename = fmt.Sprintf("terraform-provider-%s_%s", provider.Type, version.String())
|
||||||
if target.OS == "windows" {
|
if target.OS == "windows" {
|
||||||
// For a little more (technically unnecessary) realism...
|
// For a little more (technically unnecessary) realism...
|
||||||
execFilename += ".exe"
|
execFilename += ".exe"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
zw := zip.NewWriter(f)
|
zw := zip.NewWriter(f)
|
||||||
fw, err := zw.Create(execFilename)
|
fw, err := zw.Create(execFilename)
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
package providercache
|
package providercache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/internal/getproviders"
|
"github.com/hashicorp/terraform/internal/getproviders"
|
||||||
)
|
)
|
||||||
|
@ -20,16 +25,6 @@ type CachedProvider struct {
|
||||||
// both slashes and backslashes as long as the separators are consistent
|
// both slashes and backslashes as long as the separators are consistent
|
||||||
// within a particular path string.
|
// within a particular path string.
|
||||||
PackageDir string
|
PackageDir string
|
||||||
|
|
||||||
// ExecutableFile is the local filesystem path to the main plugin executable
|
|
||||||
// for the provider, which is always a file within the directory given
|
|
||||||
// in PackageDir.
|
|
||||||
//
|
|
||||||
// The path always uses slashes as path separators, even on Windows, so
|
|
||||||
// that the results are consistent between platforms. Windows accepts
|
|
||||||
// both slashes and backslashes as long as the separators are consistent
|
|
||||||
// within a particular path string.
|
|
||||||
ExecutableFile string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PackageLocation returns the package directory given in the PackageDir field
|
// PackageLocation returns the package directory given in the PackageDir field
|
||||||
|
@ -77,3 +72,70 @@ func (cp *CachedProvider) MatchesHash(want string) (bool, error) {
|
||||||
func (cp *CachedProvider) HashV1() (string, error) {
|
func (cp *CachedProvider) HashV1() (string, error) {
|
||||||
return getproviders.PackageHashV1(cp.PackageLocation())
|
return getproviders.PackageHashV1(cp.PackageLocation())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExecutableFile inspects the cached provider's unpacked package directory for
|
||||||
|
// something that looks like it's intended to be the executable file for the
|
||||||
|
// plugin.
|
||||||
|
//
|
||||||
|
// This is a bit messy and heuristic-y because historically Terraform used the
|
||||||
|
// filename itself for local filesystem discovery, allowing some variance in
|
||||||
|
// the filenames to capture extra metadata, whereas now we're using the
|
||||||
|
// directory structure leading to the executable instead but need to remain
|
||||||
|
// compatible with the executable names bundled into existing provider packages.
|
||||||
|
//
|
||||||
|
// It will return an error if it can't find a file following the expected
|
||||||
|
// convention in the given directory.
|
||||||
|
//
|
||||||
|
// If found, the path always uses slashes as path separators, even on Windows,
|
||||||
|
// so that the results are consistent between platforms. Windows accepts both
|
||||||
|
// slashes and backslashes as long as the separators are consistent within a
|
||||||
|
// particular path string.
|
||||||
|
func (cp *CachedProvider) ExecutableFile() (string, error) {
|
||||||
|
infos, err := ioutil.ReadDir(cp.PackageDir)
|
||||||
|
if err != nil {
|
||||||
|
// If the directory itself doesn't exist or isn't readable then we
|
||||||
|
// can't access an executable in it.
|
||||||
|
return "", fmt.Errorf("could not read package directory: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For a provider named e.g. tf.example.com/awesomecorp/happycloud, we
|
||||||
|
// expect an executable file whose name starts with
|
||||||
|
// "terraform-provider-happycloud", followed by zero or more additional
|
||||||
|
// characters. If there _are_ additional characters then the first one
|
||||||
|
// must be an underscore or a period, like in thse examples:
|
||||||
|
// - terraform-provider-happycloud_v1.0.0
|
||||||
|
// - terraform-provider-happycloud.exe
|
||||||
|
//
|
||||||
|
// We don't require the version in the filename to match because the
|
||||||
|
// executable's name is no longer authoritative, but packages of "official"
|
||||||
|
// providers may continue to use versioned executable names for backward
|
||||||
|
// compatibility with Terraform 0.12.
|
||||||
|
//
|
||||||
|
// We also presume that providers packaged for Windows will include the
|
||||||
|
// necessary .exe extension on their filenames but do not explicitly check
|
||||||
|
// for that. If there's a provider package for Windows that has a file
|
||||||
|
// without that suffix then it will be detected as an executable but then
|
||||||
|
// we'll presumably fail later trying to run it.
|
||||||
|
wantPrefix := "terraform-provider-" + cp.Provider.Type
|
||||||
|
|
||||||
|
// We'll visit all of the directory entries and take the first (in
|
||||||
|
// name-lexical order) that looks like a plausible provider executable
|
||||||
|
// name. A package with multiple files meeting these criteria is degenerate
|
||||||
|
// but we will tolerate it by ignoring the subsequent entries.
|
||||||
|
for _, info := range infos {
|
||||||
|
if info.IsDir() {
|
||||||
|
continue // A directory can never be an executable
|
||||||
|
}
|
||||||
|
name := info.Name()
|
||||||
|
if !strings.HasPrefix(name, wantPrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
remainder := name[len(wantPrefix):]
|
||||||
|
if len(remainder) > 0 && (remainder[0] != '_' && remainder[0] != '.') {
|
||||||
|
continue // subsequent characters must be delimited by _ or .
|
||||||
|
}
|
||||||
|
return filepath.ToSlash(filepath.Join(cp.PackageDir, name)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("could not find executable file starting with %s", wantPrefix)
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ func TestCachedProviderHash(t *testing.T) {
|
||||||
Version: getproviders.MustParseVersion("2.0.0"),
|
Version: getproviders.MustParseVersion("2.0.0"),
|
||||||
|
|
||||||
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/darwin_amd64",
|
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/darwin_amd64",
|
||||||
ExecutableFile: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/darwin_amd64/terraform-provider-null",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
want := "h1:qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g="
|
want := "h1:qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g="
|
||||||
|
@ -47,7 +46,6 @@ func TestCachedProviderHash(t *testing.T) {
|
||||||
Version: getproviders.MustParseVersion("2.0.0"),
|
Version: getproviders.MustParseVersion("2.0.0"),
|
||||||
|
|
||||||
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64",
|
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64",
|
||||||
ExecutableFile: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64/terraform-provider-null",
|
|
||||||
}
|
}
|
||||||
gotMatches, err = cp2.MatchesHash(want)
|
gotMatches, err = cp2.MatchesHash(want)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -58,3 +56,58 @@ func TestCachedProviderHash(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExecutableFile(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
cp *CachedProvider
|
||||||
|
file string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
"linux": {
|
||||||
|
cp: &CachedProvider{
|
||||||
|
Provider: addrs.NewProvider(addrs.DefaultRegistryHost, "hashicorp", "null"),
|
||||||
|
Version: getproviders.MustParseVersion("2.0.0"),
|
||||||
|
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/linux_amd64",
|
||||||
|
},
|
||||||
|
file: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/linux_amd64/terraform-provider-null",
|
||||||
|
},
|
||||||
|
"windows": {
|
||||||
|
cp: &CachedProvider{
|
||||||
|
Provider: addrs.NewProvider(addrs.DefaultRegistryHost, "hashicorp", "null"),
|
||||||
|
Version: getproviders.MustParseVersion("2.0.0"),
|
||||||
|
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64",
|
||||||
|
},
|
||||||
|
file: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64/terraform-provider-null.exe",
|
||||||
|
},
|
||||||
|
"missing-executable": {
|
||||||
|
cp: &CachedProvider{
|
||||||
|
Provider: addrs.NewProvider(addrs.DefaultRegistryHost, "missing", "executable"),
|
||||||
|
Version: getproviders.MustParseVersion("2.0.0"),
|
||||||
|
PackageDir: "testdata/cachedir/registry.terraform.io/missing/executable/2.0.0/linux_amd64",
|
||||||
|
},
|
||||||
|
err: "could not find executable file starting with terraform-provider-executable",
|
||||||
|
},
|
||||||
|
"missing-dir": {
|
||||||
|
cp: &CachedProvider{
|
||||||
|
Provider: addrs.NewProvider(addrs.DefaultRegistryHost, "missing", "packagedir"),
|
||||||
|
Version: getproviders.MustParseVersion("2.0.0"),
|
||||||
|
PackageDir: "testdata/cachedir/registry.terraform.io/missing/packagedir/2.0.0/linux_amd64",
|
||||||
|
},
|
||||||
|
err: "could not read package directory: open testdata/cachedir/registry.terraform.io/missing/packagedir/2.0.0/linux_amd64: no such file or directory",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
file, err := tc.cp.ExecutableFile()
|
||||||
|
if file != tc.file {
|
||||||
|
t.Errorf("wrong file\n got: %q\nwant: %q", file, tc.file)
|
||||||
|
}
|
||||||
|
if err == nil && tc.err != "" {
|
||||||
|
t.Fatalf("no error returned, want: %q", tc.err)
|
||||||
|
} else if err != nil && err.Error() != tc.err {
|
||||||
|
t.Errorf("wrong error\n got: %q\nwant: %q", err, tc.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package providercache
|
package providercache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/internal/getproviders"
|
"github.com/hashicorp/terraform/internal/getproviders"
|
||||||
|
@ -165,20 +163,12 @@ func (d *Dir) fillMetaCache() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
packageDir := filepath.Clean(string(meta.Location.(getproviders.PackageLocalDir)))
|
packageDir := filepath.Clean(string(meta.Location.(getproviders.PackageLocalDir)))
|
||||||
execFile := findProviderExecutableInLocalPackage(meta)
|
|
||||||
if execFile == "" {
|
|
||||||
// If the package doesn't contain a suitable executable then
|
|
||||||
// it isn't considered to be part of our cache.
|
|
||||||
log.Printf("[TRACE] providercache.fillMetaCache: ignoring %s because it is does not seem to contain a suitable plugin executable", meta.Location)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[TRACE] providercache.fillMetaCache: including %s as a candidate package for %s %s", meta.Location, providerAddr, meta.Version)
|
log.Printf("[TRACE] providercache.fillMetaCache: including %s as a candidate package for %s %s", meta.Location, providerAddr, meta.Version)
|
||||||
data[providerAddr] = append(data[providerAddr], CachedProvider{
|
data[providerAddr] = append(data[providerAddr], CachedProvider{
|
||||||
Provider: providerAddr,
|
Provider: providerAddr,
|
||||||
Version: meta.Version,
|
Version: meta.Version,
|
||||||
PackageDir: filepath.ToSlash(packageDir),
|
PackageDir: filepath.ToSlash(packageDir),
|
||||||
ExecutableFile: filepath.ToSlash(execFile),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,75 +190,3 @@ func (d *Dir) fillMetaCache() error {
|
||||||
d.metaCache = data
|
d.metaCache = data
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a helper function to peep into the unpacked directory associated
|
|
||||||
// with the given package meta and find something that looks like it's intended
|
|
||||||
// to be the executable file for the plugin.
|
|
||||||
//
|
|
||||||
// This is a bit messy and heuristic-y because historically Terraform used the
|
|
||||||
// filename itself for local filesystem discovery, allowing some variance in
|
|
||||||
// the filenames to capture extra metadata, whereas now we're using the
|
|
||||||
// directory structure leading to the executable instead but need to remain
|
|
||||||
// compatible with the executable names bundled into existing provider packages.
|
|
||||||
//
|
|
||||||
// It will return a zero-length string if it can't find a file following
|
|
||||||
// the expected convention in the given directory.
|
|
||||||
func findProviderExecutableInLocalPackage(meta getproviders.PackageMeta) string {
|
|
||||||
packageDir, ok := meta.Location.(getproviders.PackageLocalDir)
|
|
||||||
if !ok {
|
|
||||||
// This should never happen because the providercache package only
|
|
||||||
// uses the local unpacked directory layout. If anything else ends
|
|
||||||
// up in here then we'll indicate that no executable is available,
|
|
||||||
// because all other locations require a fetch/unpack step first.
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
infos, err := ioutil.ReadDir(string(packageDir))
|
|
||||||
if err != nil {
|
|
||||||
// If the directory itself doesn't exist or isn't readable then we
|
|
||||||
// can't access an executable in it.
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// For a provider named e.g. tf.example.com/awesomecorp/happycloud, we
|
|
||||||
// expect an executable file whose name starts with
|
|
||||||
// "terraform-provider-happycloud", followed by zero or more additional
|
|
||||||
// characters. If there _are_ additional characters then the first one
|
|
||||||
// must be an underscore or a period, like in thse examples:
|
|
||||||
// - terraform-provider-happycloud_v1.0.0
|
|
||||||
// - terraform-provider-happycloud.exe
|
|
||||||
//
|
|
||||||
// We don't require the version in the filename to match because the
|
|
||||||
// executable's name is no longer authoritative, but packages of "official"
|
|
||||||
// providers may continue to use versioned executable names for backward
|
|
||||||
// compatibility with Terraform 0.12.
|
|
||||||
//
|
|
||||||
// We also presume that providers packaged for Windows will include the
|
|
||||||
// necessary .exe extension on their filenames but do not explicitly check
|
|
||||||
// for that. If there's a provider package for Windows that has a file
|
|
||||||
// without that suffix then it will be detected as an executable but then
|
|
||||||
// we'll presumably fail later trying to run it.
|
|
||||||
wantPrefix := "terraform-provider-" + meta.Provider.Type
|
|
||||||
|
|
||||||
// We'll visit all of the directory entries and take the first (in
|
|
||||||
// name-lexical order) that looks like a plausible provider executable
|
|
||||||
// name. A package with multiple files meeting these criteria is degenerate
|
|
||||||
// but we will tolerate it by ignoring the subsequent entries.
|
|
||||||
for _, info := range infos {
|
|
||||||
if info.IsDir() {
|
|
||||||
continue // A directory can never be an executable
|
|
||||||
}
|
|
||||||
name := info.Name()
|
|
||||||
if !strings.HasPrefix(name, wantPrefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
remainder := name[len(wantPrefix):]
|
|
||||||
if len(remainder) > 0 && (remainder[0] != '_' && remainder[0] != '.') {
|
|
||||||
continue // subsequent characters must be delimited by _
|
|
||||||
}
|
|
||||||
return filepath.Join(string(packageDir), name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we fall out here then nothing has matched.
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
|
@ -59,7 +59,6 @@ func TestInstallPackage(t *testing.T) {
|
||||||
Version: versions.MustParseVersion("2.1.0"),
|
Version: versions.MustParseVersion("2.1.0"),
|
||||||
|
|
||||||
PackageDir: tmpDirPath + "/registry.terraform.io/hashicorp/null/2.1.0/linux_amd64",
|
PackageDir: tmpDirPath + "/registry.terraform.io/hashicorp/null/2.1.0/linux_amd64",
|
||||||
ExecutableFile: tmpDirPath + "/registry.terraform.io/hashicorp/null/2.1.0/linux_amd64/terraform-provider-null",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -102,7 +101,6 @@ func TestLinkFromOtherCache(t *testing.T) {
|
||||||
Version: versions.MustParseVersion("2.0.0"),
|
Version: versions.MustParseVersion("2.0.0"),
|
||||||
|
|
||||||
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64",
|
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64",
|
||||||
ExecutableFile: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64/terraform-provider-null.exe",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -139,7 +137,6 @@ func TestLinkFromOtherCache(t *testing.T) {
|
||||||
Version: versions.MustParseVersion("2.0.0"),
|
Version: versions.MustParseVersion("2.0.0"),
|
||||||
|
|
||||||
PackageDir: tmpDirPath + "/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64",
|
PackageDir: tmpDirPath + "/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64",
|
||||||
ExecutableFile: tmpDirPath + "/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64/terraform-provider-null.exe",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,9 @@ func TestDirReading(t *testing.T) {
|
||||||
addrs.DefaultRegistryHost, "bloop", "nonexist",
|
addrs.DefaultRegistryHost, "bloop", "nonexist",
|
||||||
)
|
)
|
||||||
legacyProvider := addrs.NewLegacyProvider("legacy")
|
legacyProvider := addrs.NewLegacyProvider("legacy")
|
||||||
|
missingExecutableProvider := addrs.NewProvider(
|
||||||
|
addrs.DefaultRegistryHost, "missing", "executable",
|
||||||
|
)
|
||||||
|
|
||||||
t.Run("ProviderLatestVersion", func(t *testing.T) {
|
t.Run("ProviderLatestVersion", func(t *testing.T) {
|
||||||
t.Run("exists", func(t *testing.T) {
|
t.Run("exists", func(t *testing.T) {
|
||||||
|
@ -51,7 +54,6 @@ func TestDirReading(t *testing.T) {
|
||||||
Version: versions.MustParseVersion("2.0.0"),
|
Version: versions.MustParseVersion("2.0.0"),
|
||||||
|
|
||||||
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64",
|
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64",
|
||||||
ExecutableFile: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64/terraform-provider-null.exe",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if diff := cmp.Diff(want, got); diff != "" {
|
if diff := cmp.Diff(want, got); diff != "" {
|
||||||
|
@ -92,7 +94,6 @@ func TestDirReading(t *testing.T) {
|
||||||
Version: versions.MustParseVersion("2.0.0"),
|
Version: versions.MustParseVersion("2.0.0"),
|
||||||
|
|
||||||
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64",
|
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64",
|
||||||
ExecutableFile: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/windows_amd64/terraform-provider-null.exe",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if diff := cmp.Diff(want, got); diff != "" {
|
if diff := cmp.Diff(want, got); diff != "" {
|
||||||
|
@ -144,7 +145,6 @@ func TestDirReading(t *testing.T) {
|
||||||
Provider: legacyProvider,
|
Provider: legacyProvider,
|
||||||
Version: versions.MustParseVersion("1.0.0"),
|
Version: versions.MustParseVersion("1.0.0"),
|
||||||
PackageDir: "testdata/cachedir/registry.terraform.io/-/legacy/1.0.0/linux_amd64",
|
PackageDir: "testdata/cachedir/registry.terraform.io/-/legacy/1.0.0/linux_amd64",
|
||||||
ExecutableFile: "testdata/cachedir/registry.terraform.io/-/legacy/1.0.0/linux_amd64/terraform-provider-legacy",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nullProvider: {
|
nullProvider: {
|
||||||
|
@ -152,7 +152,6 @@ func TestDirReading(t *testing.T) {
|
||||||
Provider: nullProvider,
|
Provider: nullProvider,
|
||||||
Version: versions.MustParseVersion("2.0.0"),
|
Version: versions.MustParseVersion("2.0.0"),
|
||||||
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/linux_amd64",
|
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/linux_amd64",
|
||||||
ExecutableFile: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/linux_amd64/terraform-provider-null",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
randomProvider: {
|
randomProvider: {
|
||||||
|
@ -160,7 +159,6 @@ func TestDirReading(t *testing.T) {
|
||||||
Provider: randomProvider,
|
Provider: randomProvider,
|
||||||
Version: versions.MustParseVersion("1.2.0"),
|
Version: versions.MustParseVersion("1.2.0"),
|
||||||
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/random/1.2.0/linux_amd64",
|
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/random/1.2.0/linux_amd64",
|
||||||
ExecutableFile: "testdata/cachedir/registry.terraform.io/hashicorp/random/1.2.0/linux_amd64/terraform-provider-random",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
randomBetaProvider: {
|
randomBetaProvider: {
|
||||||
|
@ -168,7 +166,13 @@ func TestDirReading(t *testing.T) {
|
||||||
Provider: randomBetaProvider,
|
Provider: randomBetaProvider,
|
||||||
Version: versions.MustParseVersion("1.2.0"),
|
Version: versions.MustParseVersion("1.2.0"),
|
||||||
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/random-beta/1.2.0/linux_amd64",
|
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/random-beta/1.2.0/linux_amd64",
|
||||||
ExecutableFile: "testdata/cachedir/registry.terraform.io/hashicorp/random-beta/1.2.0/linux_amd64/terraform-provider-random-beta",
|
},
|
||||||
|
},
|
||||||
|
missingExecutableProvider: {
|
||||||
|
{
|
||||||
|
Provider: missingExecutableProvider,
|
||||||
|
Version: versions.MustParseVersion("2.0.0"),
|
||||||
|
PackageDir: "testdata/cachedir/registry.terraform.io/missing/executable/2.0.0/linux_amd64",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -400,6 +400,14 @@ NeedProvider:
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if _, err := cached.ExecutableFile(); err != nil {
|
||||||
|
err := fmt.Errorf("provider binary not found: %s", err)
|
||||||
|
errs[provider] = err
|
||||||
|
if cb := evts.HashPackageFailure; cb != nil {
|
||||||
|
cb(provider, version, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
hash, err := cached.Hash()
|
hash, err := cached.Hash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs[provider] = fmt.Errorf("failed to calculate checksum for installed provider %s package: %s", provider, err)
|
errs[provider] = fmt.Errorf("failed to calculate checksum for installed provider %s package: %s", provider, err)
|
||||||
|
|
|
@ -11,12 +11,102 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
svchost "github.com/hashicorp/terraform-svchost"
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
"github.com/hashicorp/terraform-svchost/disco"
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/internal/getproviders"
|
"github.com/hashicorp/terraform/internal/getproviders"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestEnsureProviderVersions_local_source(t *testing.T) {
|
||||||
|
// create filesystem source using the test provider cache dir
|
||||||
|
source := getproviders.NewFilesystemMirrorSource("testdata/cachedir")
|
||||||
|
|
||||||
|
// create a temporary workdir
|
||||||
|
tmpDirPath, err := ioutil.TempDir("", "terraform-test-providercache")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDirPath)
|
||||||
|
|
||||||
|
// set up the installer using the temporary directory and filesystem source
|
||||||
|
platform := getproviders.Platform{OS: "linux", Arch: "amd64"}
|
||||||
|
dir := NewDirWithPlatform(tmpDirPath, platform)
|
||||||
|
installer := NewInstaller(dir, source)
|
||||||
|
|
||||||
|
tests := map[string]struct {
|
||||||
|
provider string
|
||||||
|
version string
|
||||||
|
installed bool
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
"install-unpacked": {
|
||||||
|
provider: "null",
|
||||||
|
version: "2.0.0",
|
||||||
|
installed: true,
|
||||||
|
},
|
||||||
|
"invalid-zip-file": {
|
||||||
|
provider: "null",
|
||||||
|
version: "2.1.0",
|
||||||
|
installed: false,
|
||||||
|
err: "zip: not a valid zip file",
|
||||||
|
},
|
||||||
|
"version-constraint-unmet": {
|
||||||
|
provider: "null",
|
||||||
|
version: "2.2.0",
|
||||||
|
installed: false,
|
||||||
|
err: "no available releases match the given constraints 2.2.0",
|
||||||
|
},
|
||||||
|
"missing-executable": {
|
||||||
|
provider: "missing/executable",
|
||||||
|
version: "2.0.0",
|
||||||
|
installed: true,
|
||||||
|
err: "provider binary not found: could not find executable file starting with terraform-provider-executable",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
ctx := context.TODO()
|
||||||
|
|
||||||
|
provider := addrs.MustParseProviderSourceString(test.provider)
|
||||||
|
versionConstraint := getproviders.MustParseVersionConstraints(test.version)
|
||||||
|
version := getproviders.MustParseVersion(test.version)
|
||||||
|
reqs := getproviders.Requirements{
|
||||||
|
provider: versionConstraint,
|
||||||
|
}
|
||||||
|
wantSelected := getproviders.Selections{provider: version}
|
||||||
|
if !test.installed {
|
||||||
|
wantSelected = getproviders.Selections{}
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := installer.EnsureProviderVersions(ctx, reqs, InstallNewProvidersOnly)
|
||||||
|
|
||||||
|
if diff := cmp.Diff(wantSelected, selected); diff != "" {
|
||||||
|
t.Errorf("wrong selected\n%s", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.err == "" && err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch err := err.(type) {
|
||||||
|
case InstallerError:
|
||||||
|
providerError, ok := err.ProviderErrors[provider]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("did not get error for provider %s", provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := providerError.Error(); got != test.err {
|
||||||
|
t.Fatalf("wrong result\ngot: %s\nwant: %s\n", got, test.err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatalf("wrong error type. Expected InstallerError, got %T", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This test only verifies protocol errors and does not try for successfull
|
// This test only verifies protocol errors and does not try for successfull
|
||||||
// installation (at the time of writing, the test files aren't signed so the
|
// installation (at the time of writing, the test files aren't signed so the
|
||||||
// signature verification fails); that's left to the e2e tests.
|
// signature verification fails); that's left to the e2e tests.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
This file represents a misnamed provider executable.
|
Loading…
Reference in New Issue