From be2069ac81b0ee8f6e5902185d8decb08be87df9 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 15 Jun 2017 15:23:16 -0400 Subject: [PATCH] add -plugin-dir option The -plugin-dir option lets the user specify custom search paths for plugins. This overrides all other plugin search paths, and prevents the auto-installation of plugins. We also make sure that the availability of plugins is always checked during init, even if -get-plugins=false or -plugin-dir is set. --- command/init.go | 86 ++++++++++++++++++++-------------- command/init_test.go | 101 +++++++++++++++++++++++++++++++++++++++- command/plugins.go | 3 ++ command/plugins_test.go | 25 ++++++++++ 4 files changed, 179 insertions(+), 36 deletions(-) diff --git a/command/init.go b/command/init.go index c98eb1f90..69c0ec3aa 100644 --- a/command/init.go +++ b/command/init.go @@ -22,6 +22,9 @@ import ( type InitCommand struct { Meta + // getPlugins is for the -get-plugins flag + getPlugins bool + // providerInstaller is used to download and install providers that // aren't found locally. This uses a discovery.ProviderInstaller instance // by default, but it can be overridden here as a way to mock fetching @@ -30,7 +33,7 @@ type InitCommand struct { } func (c *InitCommand) Run(args []string) int { - var flagBackend, flagGet, flagGetPlugins, flagUpgrade bool + var flagBackend, flagGet, flagUpgrade bool var flagConfigExtra map[string]interface{} var flagPluginPath FlagStringSlice @@ -39,7 +42,7 @@ func (c *InitCommand) Run(args []string) int { cmdFlags.BoolVar(&flagBackend, "backend", true, "") cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "") cmdFlags.BoolVar(&flagGet, "get", true, "") - cmdFlags.BoolVar(&flagGetPlugins, "get-plugins", true, "") + cmdFlags.BoolVar(&c.getPlugins, "get-plugins", true, "") cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data") cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") @@ -52,7 +55,10 @@ func (c *InitCommand) Run(args []string) int { return 1 } - c.pluginPath = flagPluginPath + if len(flagPluginPath) > 0 { + c.pluginPath = flagPluginPath + c.getPlugins = false + } // set getProvider if we don't have a test version already if c.providerInstaller == nil { @@ -141,7 +147,7 @@ func (c *InitCommand) Run(args []string) int { // If we're requesting backend configuration or looking for required // plugins, load the backend - if flagBackend || flagGetPlugins { + if flagBackend { header = true // Only output that we're initializing a backend if we have @@ -165,31 +171,29 @@ func (c *InitCommand) Run(args []string) int { } } - // Now that we have loaded all modules, check the module tree for missing providers - if flagGetPlugins { - sMgr, err := back.State(c.Workspace()) - if err != nil { - c.Ui.Error(fmt.Sprintf( - "Error loading state: %s", err)) - return 1 - } + // Now that we have loaded all modules, check the module tree for missing providers. + sMgr, err := back.State(c.Workspace()) + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error loading state: %s", err)) + return 1 + } - if err := sMgr.RefreshState(); err != nil { - c.Ui.Error(fmt.Sprintf( - "Error refreshing state: %s", err)) - return 1 - } + if err := sMgr.RefreshState(); err != nil { + c.Ui.Error(fmt.Sprintf( + "Error refreshing state: %s", err)) + return 1 + } - c.Ui.Output(c.Colorize().Color( - "[reset][bold]Initializing provider plugins...", - )) + c.Ui.Output(c.Colorize().Color( + "[reset][bold]Initializing provider plugins...", + )) - err = c.getProviders(path, sMgr.State(), flagUpgrade) - if err != nil { - // this function provides its own output - log.Printf("[ERROR] %s", err) - return 1 - } + err = c.getProviders(path, sMgr.State(), flagUpgrade) + if err != nil { + // this function provides its own output + log.Printf("[ERROR] %s", err) + return 1 } // If we outputted information, then we need to output a newline @@ -229,17 +233,26 @@ func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade missing := c.missingPlugins(available, requirements) var errs error - for provider, reqd := range missing { - c.Ui.Output(fmt.Sprintf("- downloading plugin for provider %q...", provider)) - _, err := c.providerInstaller.Get(provider, reqd.Versions) + if c.getPlugins { + for provider, reqd := range missing { + c.Ui.Output(fmt.Sprintf("- downloading plugin for provider %q...", provider)) + _, err := c.providerInstaller.Get(provider, reqd.Versions) - if err != nil { - c.Ui.Error(fmt.Sprintf(errProviderNotFound, err, provider, reqd.Versions)) - errs = multierror.Append(errs, err) + if err != nil { + c.Ui.Error(fmt.Sprintf(errProviderNotFound, err, provider, reqd.Versions)) + errs = multierror.Append(errs, err) + } } - } - if errs != nil { + if errs != nil { + return errs + } + } else if len(missing) > 0 { + // we have missing providers, but aren't going to try and download them + for provider, reqd := range missing { + c.Ui.Error(fmt.Sprintf(errProviderNotFound, err, provider, reqd.Versions)) + errs = multierror.Append(errs, fmt.Errorf("missing provider %q", provider)) + } return errs } @@ -360,6 +373,11 @@ Options: -no-color If specified, output won't contain any color. + -plugin-dir Directory containing plugin binaries. This overrides all + default search paths for plugins, and prevents the + automatic installation of plugins. This flag can be used + multiple times. + -reconfigure Reconfigure the backend, ignoring any saved configuration. -upgrade=false If installing modules (-get) or plugins (-get-plugins), diff --git a/command/init_test.go b/command/init_test.go index e17ff8835..dd296d945 100644 --- a/command/init_test.go +++ b/command/init_test.go @@ -538,7 +538,7 @@ func TestInit_findVendoredProviders(t *testing.T) { if err := ioutil.WriteFile(greaterThanPath, []byte("test bin"), 0755); err != nil { t.Fatal(err) } - // TODO: change this to the -plugin-dir path + // Check the current directory too betweenPath := filepath.Join(".", "terraform-provider-between_v2.3.4_x4") if err := ioutil.WriteFile(betweenPath, []byte("test bin"), 0755); err != nil { t.Fatal(err) @@ -548,7 +548,6 @@ func TestInit_findVendoredProviders(t *testing.T) { if code := c.Run(args); code != 0 { t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) } - } func TestInit_getUpgradePlugins(t *testing.T) { @@ -767,3 +766,101 @@ func TestInit_providerLockFile(t *testing.T) { t.Errorf("wrong provider lock file contents\ngot: %s\nwant: %s", buf, wantLockFile) } } + +// Test user-supplied -plugin-dir +func TestInit_pluginDirProviders(t *testing.T) { + 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, + } + + c := &InitCommand{ + Meta: m, + providerInstaller: &mockProviderInstaller{}, + } + + // make our vendor paths + pluginPath := []string{"a", "b", "c"} + for _, p := range pluginPath { + if err := os.MkdirAll(p, 0755); err != nil { + t.Fatal(err) + } + } + + // add some dummy providers in our plugin dirs + for i, name := range []string{ + "terraform-provider-exact_v1.2.3_x4", + "terraform-provider-greater_than_v2.3.4_x4", + "terraform-provider-between_v2.3.4_x4", + } { + + if err := ioutil.WriteFile(filepath.Join(pluginPath[i], name), []byte("test bin"), 0755); err != nil { + t.Fatal(err) + } + } + + args := []string{ + "-plugin-dir", "a", + "-plugin-dir", "b", + "-plugin-dir", "c", + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter) + } +} + +// Test user-supplied -plugin-dir doesn't allow auto-install +func TestInit_pluginDirProvidersDoesNotGet(t *testing.T) { + 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, + } + + c := &InitCommand{ + Meta: m, + providerInstaller: callbackPluginInstaller(func(provider string, req discovery.Constraints) (discovery.PluginMeta, error) { + t.Fatalf("plugin installer should not have been called for %q", provider) + return discovery.PluginMeta{}, nil + }), + } + + // make our vendor paths + pluginPath := []string{"a", "b"} + for _, p := range pluginPath { + if err := os.MkdirAll(p, 0755); err != nil { + t.Fatal(err) + } + } + + // add some dummy providers in our plugin dirs + for i, name := range []string{ + "terraform-provider-exact_v1.2.3_x4", + "terraform-provider-greater_than_v2.3.4_x4", + } { + + if err := ioutil.WriteFile(filepath.Join(pluginPath[i], name), []byte("test bin"), 0755); err != nil { + t.Fatal(err) + } + } + + args := []string{ + "-plugin-dir", "a", + "-plugin-dir", "b", + } + if code := c.Run(args); code == 0 { + // should have been an error + t.Fatalf("bad: \n%s", ui.OutputWriter) + } +} diff --git a/command/plugins.go b/command/plugins.go index 07a155255..6ce642727 100644 --- a/command/plugins.go +++ b/command/plugins.go @@ -83,6 +83,9 @@ func (m *Meta) storePluginPath(pluginPath []string) error { return err } + // if this fails, so will WriteFile + os.MkdirAll(m.DataDir(), 0755) + return ioutil.WriteFile(filepath.Join(m.DataDir(), PluginPathFile), js, 0644) } diff --git a/command/plugins_test.go b/command/plugins_test.go index 2ee466d28..e0cd13c5b 100644 --- a/command/plugins_test.go +++ b/command/plugins_test.go @@ -2,12 +2,37 @@ package command import ( "fmt" + "io/ioutil" "os" "path/filepath" + "reflect" + "testing" "github.com/hashicorp/terraform/plugin/discovery" ) +func TestPluginPath(t *testing.T) { + td, err := ioutil.TempDir("", "tf") + defer os.RemoveAll(td) + defer testChdir(t, td)() + + pluginPath := []string{"a", "b", "c"} + + m := Meta{} + if err := m.storePluginPath(pluginPath); err != nil { + t.Fatal(err) + } + + restoredPath, err := m.loadPluginPath() + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(pluginPath, restoredPath) { + t.Fatalf("expected plugin path %#v, got %#v", pluginPath, restoredPath) + } +} + // mockProviderInstaller is a discovery.PluginInstaller implementation that // is a mock for discovery.ProviderInstaller. type mockProviderInstaller struct {