Merge pull request #8616 from hashicorp/packer-plugin-path

add PACKER_PLUGIN_PATH for plugin discovery
pull/8663/head
Megan Marsh 6 years ago committed by GitHub
commit 817957fe4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -130,7 +130,7 @@ func (c *config) Discover() error {
return nil return nil
} }
// First, look in the same directory as the executable. // Next, look in the same directory as the executable.
exePath, err := osext.Executable() exePath, err := osext.Executable()
if err != nil { if err != nil {
log.Printf("[ERR] Error loading exe directory: %s", err) log.Printf("[ERR] Error loading exe directory: %s", err)
@ -140,7 +140,7 @@ func (c *config) Discover() error {
} }
} }
// Next, look in the plugins directory. // Next, look in the default plugins directory inside the configdir/.packer.d/plugins.
dir, err := packer.ConfigDir() dir, err := packer.ConfigDir()
if err != nil { if err != nil {
log.Printf("[ERR] Error loading config directory: %s", err) log.Printf("[ERR] Error loading config directory: %s", err)
@ -155,6 +155,22 @@ func (c *config) Discover() error {
return err return err
} }
// Check whether there is a custom Plugin directory defined. This gets
// absolute preference.
if packerPluginPath := os.Getenv("PACKER_PLUGIN_PATH"); packerPluginPath != "" {
sep := ":"
if runtime.GOOS == "windows" {
// on windows, PATH is semicolon-separated
sep = ";"
}
plugPaths := strings.Split(packerPluginPath, sep)
for _, plugPath := range plugPaths {
if err := c.discoverExternalComponents(plugPath); err != nil {
return err
}
}
}
// Finally, try to use an internal plugin. Note that this will not override // Finally, try to use an internal plugin. Note that this will not override
// any previously-loaded plugins. // any previously-loaded plugins.
if err := c.discoverInternalComponents(); err != nil { if err := c.discoverInternalComponents(); err != nil {

@ -12,8 +12,107 @@ import (
"testing" "testing"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/plugin"
) )
func newConfig() config {
var conf config
conf.PluginMinPort = 10000
conf.PluginMaxPort = 25000
conf.Builders = packer.MapOfBuilder{}
conf.PostProcessors = packer.MapOfPostProcessor{}
conf.Provisioners = packer.MapOfProvisioner{}
return conf
}
func TestDiscoverReturnsIfMagicCookieSet(t *testing.T) {
config := newConfig()
os.Setenv(plugin.MagicCookieKey, plugin.MagicCookieValue)
defer os.Unsetenv(plugin.MagicCookieKey)
err := config.Discover()
if err != nil {
t.Fatalf("Should not have errored: %s", err)
}
if len(config.Builders) != 0 {
t.Fatalf("Should not have tried to find builders")
}
}
func TestEnvVarPackerPluginPath(t *testing.T) {
// Create a temporary directory to store plugins in
dir, _, cleanUpFunc, err := generateFakePlugins("custom_plugin_dir",
[]string{"packer-provisioner-partyparrot"})
if err != nil {
t.Fatalf("Error creating fake custom plugins: %s", err)
}
defer cleanUpFunc()
// Add temp dir to path.
os.Setenv("PACKER_PLUGIN_PATH", dir)
defer os.Unsetenv("PACKER_PLUGIN_PATH")
config := newConfig()
err = config.Discover()
if err != nil {
t.Fatalf("Should not have errored: %s", err)
}
if len(config.Provisioners) == 0 {
t.Fatalf("Should have found partyparrot provisioner")
}
if _, ok := config.Provisioners["partyparrot"]; !ok {
t.Fatalf("Should have found partyparrot provisioner.")
}
}
func TestEnvVarPackerPluginPath_MultiplePaths(t *testing.T) {
// Create a temporary directory to store plugins in
dir, _, cleanUpFunc, err := generateFakePlugins("custom_plugin_dir",
[]string{"packer-provisioner-partyparrot"})
if err != nil {
t.Fatalf("Error creating fake custom plugins: %s", err)
}
defer cleanUpFunc()
pathsep := ":"
if runtime.GOOS == "windows" {
pathsep = ";"
}
// Create a second dir to look in that will be empty
decoyDir, err := ioutil.TempDir("", "decoy")
if err != nil {
t.Fatalf("Failed to create a temporary test dir.")
}
defer os.Remove(decoyDir)
pluginPath := dir + pathsep + decoyDir
// Add temp dir to path.
os.Setenv("PACKER_PLUGIN_PATH", pluginPath)
defer os.Unsetenv("PACKER_PLUGIN_PATH")
config := newConfig()
err = config.Discover()
if err != nil {
t.Fatalf("Should not have errored: %s", err)
}
if len(config.Provisioners) == 0 {
t.Fatalf("Should have found partyparrot provisioner")
}
if _, ok := config.Provisioners["partyparrot"]; !ok {
t.Fatalf("Should have found partyparrot provisioner.")
}
}
func TestDecodeConfig(t *testing.T) { func TestDecodeConfig(t *testing.T) {
packerConfig := ` packerConfig := `
@ -145,18 +244,13 @@ func TestLoadSingleComponent(t *testing.T) {
} }
/* generateFakePackerConfigData creates a collection of mock plugins along with a basic packerconfig. func generateFakePlugins(dirname string, pluginNames []string) (string, []string, func(), error) {
The return packerConfigData is a valid packerconfig file that can be used for configuring external plugins, cleanUpFunc is a function that should be called for cleaning up any generated mock data. dir, err := ioutil.TempDir("", dirname)
This function will only clean up if there is an error, on successful runs the caller
is responsible for cleaning up the data via cleanUpFunc().
*/
func generateFakePackerConfigData() (packerConfigData string, cleanUpFunc func(), err error) {
dir, err := ioutil.TempDir("", "random-testdata")
if err != nil { if err != nil {
return "", nil, fmt.Errorf("failed to create temporary test directory: %v", err) return "", nil, nil, fmt.Errorf("failed to create temporary test directory: %v", err)
} }
cleanUpFunc = func() { cleanUpFunc := func() {
os.RemoveAll(dir) os.RemoveAll(dir)
} }
@ -165,19 +259,36 @@ func generateFakePackerConfigData() (packerConfigData string, cleanUpFunc func()
suffix = ".exe" suffix = ".exe"
} }
plugins := [...]string{ plugins := make([]string, len(pluginNames))
filepath.Join(dir, "packer-builder-cloud-xyz"+suffix), for i, plugin := range pluginNames {
filepath.Join(dir, "packer-provisioner-super-shell"+suffix), plug := filepath.Join(dir, plugin+suffix)
filepath.Join(dir, "packer-post-processor-noop"+suffix), plugins[i] = plug
} _, err := os.Create(plug)
for _, plugin := range plugins {
_, err := os.Create(plugin)
if err != nil { if err != nil {
cleanUpFunc() cleanUpFunc()
return "", nil, fmt.Errorf("failed to create temporary plugin file (%s): %v", plugin, err) return "", nil, nil, fmt.Errorf("failed to create temporary plugin file (%s): %v", plug, err)
} }
} }
return dir, plugins, cleanUpFunc, nil
}
/* generateFakePackerConfigData creates a collection of mock plugins along with a basic packerconfig.
The return packerConfigData is a valid packerconfig file that can be used for configuring external plugins, cleanUpFunc is a function that should be called for cleaning up any generated mock data.
This function will only clean up if there is an error, on successful runs the caller
is responsible for cleaning up the data via cleanUpFunc().
*/
func generateFakePackerConfigData() (packerConfigData string, cleanUpFunc func(), err error) {
_, plugins, cleanUpFunc, err := generateFakePlugins("random-testdata",
[]string{"packer-builder-cloud-xyz",
"packer-provisioner-super-shell",
"packer-post-processor-noop"})
if err != nil {
cleanUpFunc()
return "", nil, err
}
packerConfigData = fmt.Sprintf(` packerConfigData = fmt.Sprintf(`
{ {
"PluginMinPort": 10, "PluginMinPort": 10,

@ -51,6 +51,7 @@ Once the plugin is named properly, Packer automatically discovers plugins in
the following directories in the given order. If a conflicting plugin is found the following directories in the given order. If a conflicting plugin is found
later, it will take precedence over one found earlier. later, it will take precedence over one found earlier.
1. The directory where `packer` is, or the executable directory. 1. The directory where `packer` is, or the executable directory.
2. The `$HOME/.packer.d/plugins` directory, if `$HOME` is defined (unix) 2. The `$HOME/.packer.d/plugins` directory, if `$HOME` is defined (unix)
@ -62,6 +63,14 @@ later, it will take precedence over one found earlier.
5. The current working directory. 5. The current working directory.
6. The directory defined in the env var `PACKER_PLUGIN_PATH`. There can be more
than one directory defined; for example, `~/custom-dir-1:~/custom-dir-2`.
Separate directories in the PATH string using a colon (`:`) on posix systems and
a semicolon (`;`) on windows systems. The above example path would be able to
find a provisioner named `packer-provisioner-foo` in either
`~/custom-dir-1/packer-provisioner-foo` or
`~/custom-dir-2/packer-provisioner-foo`.
The valid types for plugins are: The valid types for plugins are:
- `builder` - Plugins responsible for building images for a specific - `builder` - Plugins responsible for building images for a specific

@ -39,6 +39,14 @@ each can be found below:
connections on your local host. The default is 10,000. See the [core connections on your local host. The default is 10,000. See the [core
configuration page](/docs/other/core-configuration.html). configuration page](/docs/other/core-configuration.html).
- `PACKER_PLUGIN_PATH` - a PATH variable for finding third-party packer
plugins. For example: `~/custom-dir-1:~/custom-dir-2`.
Separate directories in the PATH string using a colon (`:`) on posix systems and
a semicolon (`;`) on windows systems. The above example path would be able to
find a provisioner named `packer-provisioner-foo` in either
`~/custom-dir-1/packer-provisioner-foo` or
`~/custom-dir-2/packer-provisioner-foo`.
- `CHECKPOINT_DISABLE` - When Packer is invoked it sometimes calls out to - `CHECKPOINT_DISABLE` - When Packer is invoked it sometimes calls out to
[checkpoint.hashicorp.com](https://checkpoint.hashicorp.com/) to look for [checkpoint.hashicorp.com](https://checkpoint.hashicorp.com/) to look for
new versions of Packer. If you want to disable this for security or privacy new versions of Packer. If you want to disable this for security or privacy

Loading…
Cancel
Save