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

add PACKER_PLUGIN_PATH for plugin discovery
This commit is contained in:
Megan Marsh 2020-01-28 12:09:55 -08:00 committed by GitHub
commit 817957fe4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 163 additions and 19 deletions

View File

@ -130,7 +130,7 @@ func (c *config) Discover() error {
return nil
}
// First, look in the same directory as the executable.
// Next, look in the same directory as the executable.
exePath, err := osext.Executable()
if err != nil {
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()
if err != nil {
log.Printf("[ERR] Error loading config directory: %s", err)
@ -155,6 +155,22 @@ func (c *config) Discover() error {
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
// any previously-loaded plugins.
if err := c.discoverInternalComponents(); err != nil {

View File

@ -12,8 +12,107 @@ import (
"testing"
"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) {
packerConfig := `
@ -145,18 +244,13 @@ func TestLoadSingleComponent(t *testing.T) {
}
/* 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) {
dir, err := ioutil.TempDir("", "random-testdata")
func generateFakePlugins(dirname string, pluginNames []string) (string, []string, func(), error) {
dir, err := ioutil.TempDir("", dirname)
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)
}
@ -165,19 +259,36 @@ func generateFakePackerConfigData() (packerConfigData string, cleanUpFunc func()
suffix = ".exe"
}
plugins := [...]string{
filepath.Join(dir, "packer-builder-cloud-xyz"+suffix),
filepath.Join(dir, "packer-provisioner-super-shell"+suffix),
filepath.Join(dir, "packer-post-processor-noop"+suffix),
}
for _, plugin := range plugins {
_, err := os.Create(plugin)
plugins := make([]string, len(pluginNames))
for i, plugin := range pluginNames {
plug := filepath.Join(dir, plugin+suffix)
plugins[i] = plug
_, err := os.Create(plug)
if err != nil {
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(`
{
"PluginMinPort": 10,

View File

@ -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
later, it will take precedence over one found earlier.
1. The directory where `packer` is, or the executable directory.
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.
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:
- `builder` - Plugins responsible for building images for a specific

View File

@ -39,6 +39,14 @@ each can be found below:
connections on your local host. The default is 10,000. See the [core
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.hashicorp.com](https://checkpoint.hashicorp.com/) to look for
new versions of Packer. If you want to disable this for security or privacy