command: init -upgrade for provider plugins

Now when -upgrade is provided to "terraform init" (and plugin installation
isn't disabled) it will:

- ignore the contents of the auto-install plugin directory when deciding
  what is "available", thus causing anything there to be reinstalled,
  possibly at a newer version.
- if installation completes successfully, purge from the auto-install
  plugin directory any plugin-looking files that aren't in the set of
  chosen plugins.

As before, plugins outside of the auto-install directory are able to
take precedence over the auto-install ones, and these will never be
upgraded nor purged.

The thinking here is that the auto-install directory is an implementation
detail directly managed by Terraform, and so it's Terraform's
responsibility to automatically keep it clean as plugins are upgraded.

We don't yet have the -plugin-dir option implemented, but once it is it
should circumvent all of this behavior and just expect providers to be
already available in the given directory, meaning that nothing will be
auto-installed, -upgraded or -purged.
This commit is contained in:
Martin Atkins 2017-06-12 18:32:42 -07:00
parent af4c82d151
commit 6979a07754
2 changed files with 116 additions and 1 deletions

View File

@ -213,7 +213,14 @@ func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade
return err return err
} }
available := c.providerPluginSet() var available discovery.PluginMetaSet
if upgrade {
// If we're in upgrade mode, we ignore any auto-installed plugins
// in "available", causing us to reinstall and possibly upgrade them.
available = c.providerPluginManuallyInstalledSet()
} else {
available = c.providerPluginSet()
}
requirements := terraform.ModuleTreeDependencies(mod, state).AllPluginRequirements() requirements := terraform.ModuleTreeDependencies(mod, state).AllPluginRequirements()
missing := c.missingPlugins(available, requirements) missing := c.missingPlugins(available, requirements)
@ -253,6 +260,20 @@ func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade
return err return err
} }
if upgrade {
// Purge any auto-installed plugins that aren't being used.
purged, err := c.providerInstaller.PurgeUnused(chosen)
if err != nil {
// Failure to purge old plugins is not a fatal error
c.Ui.Warn(fmt.Sprintf("failed to purge unused plugins: %s", err))
}
if purged != nil {
for meta := range purged {
log.Printf("[DEBUG] Purged unused %s plugin %s", meta.Name, meta.Path)
}
}
}
// If any providers have "floating" versions (completely unconstrained) // If any providers have "floating" versions (completely unconstrained)
// we'll suggest the user constrain with a pessimistic constraint to // we'll suggest the user constrain with a pessimistic constraint to
// avoid implicitly adopting a later major release. // avoid implicitly adopting a later major release.

View File

@ -5,7 +5,9 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"sort"
"strings" "strings"
"testing" "testing"
@ -492,6 +494,98 @@ func TestInit_getProvider(t *testing.T) {
} }
} }
func TestInit_getUpgradePlugins(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
copy.CopyDir(testFixturePath("init-get-providers"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
ui := new(cli.MockUi)
m := Meta{
testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui,
}
installer := &mockProviderInstaller{
Providers: map[string][]string{
// looking for an exact version
"exact": []string{"1.2.3"},
// config requires >= 2.3.3
"greater_than": []string{"2.3.4", "2.3.3", "2.3.0"},
// config specifies
"between": []string{"3.4.5", "2.3.4", "1.2.3"},
},
Dir: m.pluginDir(),
}
err := os.MkdirAll(m.pluginDir(), os.ModePerm)
if err != nil {
t.Fatal(err)
}
exactUnwanted := filepath.Join(m.pluginDir(), installer.FileName("exact", "0.0.1"))
err = ioutil.WriteFile(exactUnwanted, []byte{}, os.ModePerm)
if err != nil {
t.Fatal(err)
}
greaterThanUnwanted := filepath.Join(m.pluginDir(), installer.FileName("greater_than", "2.3.3"))
err = ioutil.WriteFile(greaterThanUnwanted, []byte{}, os.ModePerm)
if err != nil {
t.Fatal(err)
}
betweenOverride := installer.FileName("between", "2.3.4") // intentionally directly in cwd, and should override auto-install
err = ioutil.WriteFile(betweenOverride, []byte{}, os.ModePerm)
if err != nil {
t.Fatal(err)
}
c := &InitCommand{
Meta: m,
providerInstaller: installer,
}
args := []string{
"-upgrade=true",
}
if code := c.Run(args); code != 0 {
t.Fatalf("command did not complete successfully:\n%s", ui.ErrorWriter.String())
}
files, err := ioutil.ReadDir(m.pluginDir())
if err != nil {
t.Fatal(err)
}
if !installer.PurgeUnusedCalled {
t.Errorf("init -upgrade didn't purge providers, but should have")
}
gotFilenames := make([]string, len(files))
for i, info := range files {
gotFilenames[i] = info.Name()
}
sort.Strings(gotFilenames)
wantFilenames := []string{
"lock.json",
// no "between" because the file in cwd overrides it
// The mock PurgeUnused doesn't actually purge anything, so the dir
// includes both our old and new versions.
"terraform-provider-exact_v0.0.1_x4",
"terraform-provider-exact_v1.2.3_x4",
"terraform-provider-greater_than_v2.3.3_x4",
"terraform-provider-greater_than_v2.3.4_x4",
}
if !reflect.DeepEqual(gotFilenames, wantFilenames) {
t.Errorf("wrong directory contents after upgrade\ngot: %#v\nwant: %#v", gotFilenames, wantFilenames)
}
}
func TestInit_getProviderMissing(t *testing.T) { func TestInit_getProviderMissing(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)