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.
This commit is contained in:
Alisdair McDiarmid 2020-07-07 14:46:23 -04:00
parent a18b531b14
commit 3b1347ac1a
2 changed files with 98 additions and 0 deletions

View File

@ -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)

View File

@ -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.