From 3b1347ac1ab82136f8033910ad2be0206a32d7d6 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Tue, 7 Jul 2020 14:46:23 -0400 Subject: [PATCH] providercache: Validate provider executable file At the end of the EnsureProviderVersions process, we generate a lockfile of the selected and installed provider versions. This includes a hash of the unpacked provider directory. When calculating this hash and generating the lockfile, we now also verify that the provider directory contains a valid executable file. If not, we return an error for this provider and trigger the installer's HashPackageFailure event. Note that this event is not yet processed by terraform init; that comes in the next commit. --- internal/providercache/installer.go | 8 +++ internal/providercache/installer_test.go | 90 ++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/internal/providercache/installer.go b/internal/providercache/installer.go index c0fe084065..bd1d95619f 100644 --- a/internal/providercache/installer.go +++ b/internal/providercache/installer.go @@ -400,6 +400,14 @@ NeedProvider: } 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() if err != nil { errs[provider] = fmt.Errorf("failed to calculate checksum for installed provider %s package: %s", provider, err) diff --git a/internal/providercache/installer_test.go b/internal/providercache/installer_test.go index 46d9624078..22b20962de 100644 --- a/internal/providercache/installer_test.go +++ b/internal/providercache/installer_test.go @@ -11,12 +11,102 @@ import ( "strings" "testing" + "github.com/google/go-cmp/cmp" svchost "github.com/hashicorp/terraform-svchost" "github.com/hashicorp/terraform-svchost/disco" "github.com/hashicorp/terraform/addrs" "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 // installation (at the time of writing, the test files aren't signed so the // signature verification fails); that's left to the e2e tests.