diff --git a/CHANGELOG.md b/CHANGELOG.md index b8fa24198c..013f876505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## 0.2.1 (unreleased) +IMPROVEMENTS: + + * core: Plugins are automatically discovered in the executable directory + or pwd if named properly. [GH-190] + BUG FIXES: * core: Configuration parses when identifier and '=' have no space. [GH-243] diff --git a/config.go b/config.go index ab676f01fe..d6515ce231 100644 --- a/config.go +++ b/config.go @@ -3,6 +3,7 @@ package main import ( "fmt" "io/ioutil" + "log" "os" "os/exec" "path/filepath" @@ -31,22 +32,6 @@ var BuiltinConfig Config // ContextOpts are the global ContextOpts we use to initialize the CLI. var ContextOpts terraform.ContextOpts -func init() { - BuiltinConfig.Providers = map[string]string{ - "aws": "terraform-provider-aws", - "digitalocean": "terraform-provider-digitalocean", - "heroku": "terraform-provider-heroku", - "dnsimple": "terraform-provider-dnsimple", - "consul": "terraform-provider-consul", - "cloudflare": "terraform-provider-cloudflare", - } - BuiltinConfig.Provisioners = map[string]string{ - "local-exec": "terraform-provisioner-local-exec", - "remote-exec": "terraform-provisioner-remote-exec", - "file": "terraform-provisioner-file", - } -} - // ConfigFile returns the default path to the configuration file. // // On Unix-like systems this is the ".terraformrc" file in the home directory. @@ -81,6 +66,30 @@ func LoadConfig(path string) (*Config, error) { return &result, nil } +// Discover discovers plugins. +// +// This looks in the directory of the executable and the CWD, in that +// order for priority. +func (c *Config) Discover() error { + // Look in the cwd. + if err := c.discover("."); err != nil { + return err + } + + // Next, look in the same directory as the executable. Any conflicts + // will overwrite those found in our current directory. + exePath, err := osext.Executable() + if err != nil { + log.Printf("[ERR] Error loading exe directory: %s", err) + } else { + if err := c.discover(filepath.Dir(exePath)); err != nil { + return err + } + } + + return nil +} + // Merge merges two configurations and returns a third entirely // new configuration with the two merged. func (c1 *Config) Merge(c2 *Config) *Config { @@ -103,6 +112,54 @@ func (c1 *Config) Merge(c2 *Config) *Config { return &result } +func (c *Config) discover(path string) error { + var err error + err = c.discoverSingle( + filepath.Join(path, "terraform-provider-*"), &c.Providers) + if err != nil { + return err + } + + err = c.discoverSingle( + filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners) + if err != nil { + return err + } + + return nil +} + +func (c *Config) discoverSingle(glob string, m *map[string]string) error { + matches, err := filepath.Glob(glob) + if err != nil { + return err + } + + if *m == nil { + *m = make(map[string]string) + } + + for _, match := range matches { + file := filepath.Base(match) + + // If the filename has a ".", trim up to there + if idx := strings.Index(file, "."); idx >= 0 { + file = file[:idx] + } + + // Look for foo-bar-baz. The plugin name is "baz" + parts := strings.SplitN(file, "-", 3) + if len(parts) != 3 { + continue + } + + log.Printf("[DEBUG] Discoverd plugin: %s = %s", parts[2], match) + (*m)[parts[2]] = match + } + + return nil +} + // ProviderFactories returns the mapping of prefixes to // ResourceProviderFactory that can be used to instantiate a // binary-based plugin. diff --git a/main.go b/main.go index acbd17b05a..7f1dde2ce1 100644 --- a/main.go +++ b/main.go @@ -79,6 +79,10 @@ func wrappedMain() int { // Load the configuration config := BuiltinConfig + if err := config.Discover(); err != nil { + fmt.Fprintf(os.Stderr, "Error discovering plugins: %s", err) + return 1 + } // Make sure we clean up any managed plugins at the end of this defer plugin.CleanupClients()