diff --git a/plugin/discovery/get.go b/plugin/discovery/get.go index 0e82ebcb3..1ed7fadf9 100644 --- a/plugin/discovery/get.go +++ b/plugin/discovery/get.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "log" "net/http" + "os" "runtime" "strconv" "strings" @@ -14,6 +15,7 @@ import ( cleanhttp "github.com/hashicorp/go-cleanhttp" getter "github.com/hashicorp/go-getter" + multierror "github.com/hashicorp/go-multierror" ) // Releases are located by parsing the html listing from releases.hashicorp.com. @@ -55,6 +57,7 @@ func providerURL(name, version string) string { // from an online repository. type Installer interface { Get(name string, req Constraints) (PluginMeta, error) + PurgeUnused(used map[string]PluginMeta) (removed PluginMetaSet, err error) } // ProviderInstaller is an Installer implementation that knows how to @@ -140,6 +143,38 @@ func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, e return PluginMeta{}, fmt.Errorf("no versions of %q compatible with the plugin ProtocolVersion", provider) } +func (i *ProviderInstaller) PurgeUnused(used map[string]PluginMeta) (PluginMetaSet, error) { + purge := make(PluginMetaSet) + + present := FindPlugins("provider", []string{i.Dir}) + for meta := range present { + chosen, ok := used[meta.Name] + if !ok { + purge.Add(meta) + } + if chosen.Path != meta.Path { + purge.Add(meta) + } + } + + removed := make(PluginMetaSet) + var errs error + for meta := range purge { + path := meta.Path + err := os.Remove(path) + if err != nil { + errs = multierror.Append(errs, fmt.Errorf( + "failed to remove unused provider plugin %s: %s", + path, err, + )) + } else { + removed.Add(meta) + } + } + + return removed, errs +} + // Return the plugin version by making a HEAD request to the provided url func checkPlugin(url string, pluginProtocolVersion uint) bool { resp, err := httpClient.Head(url) diff --git a/plugin/discovery/get_test.go b/plugin/discovery/get_test.go index db1643a00..9fbd23035 100644 --- a/plugin/discovery/get_test.go +++ b/plugin/discovery/get_test.go @@ -108,7 +108,7 @@ func TestCheckProtocolVersions(t *testing.T) { } } -func TestProviderInstaller(t *testing.T) { +func TestProviderInstallerGet(t *testing.T) { tmpDir, err := ioutil.TempDir("", "tf-plugin") if err != nil { t.Fatal(err) @@ -161,6 +161,67 @@ func TestProviderInstaller(t *testing.T) { } +func TestProviderInstallerPurgeUnused(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "tf-plugin") + if err != nil { + t.Fatal(err) + } + + defer os.RemoveAll(tmpDir) + + unwantedPath := filepath.Join(tmpDir, "terraform-provider-test_v0.0.1_x2") + wantedPath := filepath.Join(tmpDir, "terraform-provider-test_v1.2.3_x3") + + f, err := os.Create(unwantedPath) + if err != nil { + t.Fatal(err) + } + f.Close() + f, err = os.Create(wantedPath) + if err != nil { + t.Fatal(err) + } + f.Close() + + i := &ProviderInstaller{ + Dir: tmpDir, + + PluginProtocolVersion: 3, + } + purged, err := i.PurgeUnused(map[string]PluginMeta{ + "test": PluginMeta{ + Name: "test", + Version: VersionStr("1.2.3"), + Path: wantedPath, + }, + }) + if err != nil { + t.Fatal(err) + } + + if got, want := purged.Count(), 1; got != want { + t.Errorf("wrong purged count %d; want %d", got, want) + } + if got, want := purged.Newest().Path, unwantedPath; got != want { + t.Errorf("wrong purged path %s; want %s", got, want) + } + + files, err := ioutil.ReadDir(tmpDir) + if err != nil { + t.Fatal(err) + } + + gotFilenames := make([]string, len(files)) + for i, info := range files { + gotFilenames[i] = info.Name() + } + wantFilenames := []string{"terraform-provider-test_v1.2.3_x3"} + + if !reflect.DeepEqual(gotFilenames, wantFilenames) { + t.Errorf("wrong filenames after purge\ngot: %#v\nwant: %#v", gotFilenames, wantFilenames) + } +} + const versionList = `