diff --git a/config.go b/config.go index c28d7931bb1..35ce6acd927 100644 --- a/config.go +++ b/config.go @@ -9,9 +9,6 @@ import ( "io" "log" "os" - "path/filepath" - "runtime" - "sort" "strings" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" @@ -41,7 +38,7 @@ func decodeConfig(r io.Reader, c *config) error { } // LoadExternalComponentsFromConfig loads plugins defined in RawBuilders, RawProvisioners, and RawPostProcessors. -func (c *config) LoadExternalComponentsFromConfig() { +func (c *config) LoadExternalComponentsFromConfig() error { // helper to build up list of plugin paths extractPaths := func(m map[string]string) []string { paths := make([]string, 0, len(m)) @@ -57,61 +54,20 @@ func (c *config) LoadExternalComponentsFromConfig() { pluginPaths = append(pluginPaths, extractPaths(c.RawBuilders)...) pluginPaths = append(pluginPaths, extractPaths(c.RawPostProcessors)...) - var externallyUsed = make([]string, 0, len(pluginPaths)) - for _, pluginPath := range pluginPaths { - name, err := c.loadSingleComponent(pluginPath) - if err != nil { - log.Print(err) - continue - } - - log.Printf("loaded plugin: %s = %s", name, pluginPath) - externallyUsed = append(externallyUsed, name) - } - - if len(externallyUsed) > 0 { - sort.Strings(externallyUsed) - log.Printf("using external plugins %v", externallyUsed) - } -} - -func (c *config) loadSingleComponent(path string) (string, error) { - pluginName := filepath.Base(path) - - // On Windows, ignore any plugins that don't end in .exe. - // We could do a full PATHEXT parse, but this is probably good enough. - if runtime.GOOS == "windows" && strings.ToLower(filepath.Ext(pluginName)) != ".exe" { - return "", fmt.Errorf("error loading plugin %q, no exe extension", path) - } - - if _, err := os.Stat(path); err != nil { - return "", fmt.Errorf("error loading plugin %q: %s", path, err) - } - - // If the filename has a ".", trim up to there - if idx := strings.Index(pluginName, "."); idx >= 0 { - pluginName = pluginName[:idx] + if len(pluginPaths) == 0 { + return nil } - switch { - case strings.HasPrefix(pluginName, "packer-builder-"): - pluginName = pluginName[len("packer-builder-"):] - c.Plugins.Builders.Set(pluginName, func() (packersdk.Builder, error) { - return c.Plugins.Client(path).Builder() - }) - case strings.HasPrefix(pluginName, "packer-post-processor-"): - pluginName = pluginName[len("packer-post-processor-"):] - c.Plugins.PostProcessors.Set(pluginName, func() (packersdk.PostProcessor, error) { - return c.Plugins.Client(path).PostProcessor() - }) - case strings.HasPrefix(pluginName, "packer-provisioner-"): - pluginName = pluginName[len("packer-provisioner-"):] - c.Plugins.Provisioners.Set(pluginName, func() (packersdk.Provisioner, error) { - return c.Plugins.Client(path).Provisioner() - }) + componentList := &strings.Builder{} + for _, path := range pluginPaths { + fmt.Fprintf(componentList, "- %s\n", path) } - return pluginName, nil + return fmt.Errorf("Your configuration file describes some legacy components: \n%s"+ + "Packer does not support these mono-component plugins anymore.\n"+ + "Please refer to our Installing Plugins docs for an overview of how to manage installation of local plugins:\n"+ + "https://developer.hashicorp.com/packer/docs/plugins/install-plugins", + componentList.String()) } // This is a proper packer.BuilderFunc that can be used to load packersdk.Builder diff --git a/config_test.go b/config_test.go index ac101010c3c..d270598a563 100644 --- a/config_test.go +++ b/config_test.go @@ -5,15 +5,9 @@ package main import ( "encoding/json" - "fmt" - "os" - "path/filepath" "reflect" - "runtime" "strings" "testing" - - "github.com/hashicorp/packer/packer" ) func TestDecodeConfig(t *testing.T) { @@ -42,185 +36,3 @@ func TestDecodeConfig(t *testing.T) { } } - -func TestLoadExternalComponentsFromConfig(t *testing.T) { - packerConfigData, cleanUpFunc, err := generateFakePackerConfigData() - if err != nil { - t.Fatalf("error encountered while creating fake Packer configuration data %v", err) - } - defer cleanUpFunc() - - cfg := config{ - Plugins: &packer.PluginConfig{ - Builders: packer.MapOfBuilder{}, - PostProcessors: packer.MapOfPostProcessor{}, - Provisioners: packer.MapOfProvisioner{}, - }, - } - - if err := decodeConfig(strings.NewReader(packerConfigData), &cfg); err != nil { - t.Fatalf("error encountered decoding configuration: %v", err) - } - - cfg.LoadExternalComponentsFromConfig() - - if len(cfg.Plugins.Builders.List()) != 1 || !cfg.Plugins.Builders.Has("cloud-xyz") { - t.Errorf("failed to load external builders; got %v as the resulting config", cfg.Plugins.Builders) - } - - if len(cfg.Plugins.PostProcessors.List()) != 1 || !cfg.Plugins.PostProcessors.Has("noop") { - t.Errorf("failed to load external post-processors; got %v as the resulting config", cfg.Plugins.PostProcessors) - } - - if len(cfg.Plugins.Provisioners.List()) != 1 || !cfg.Plugins.Provisioners.Has("super-shell") { - t.Errorf("failed to load external provisioners; got %v as the resulting config", cfg.Plugins.Provisioners) - } - -} - -func TestLoadExternalComponentsFromConfig_onlyProvisioner(t *testing.T) { - packerConfigData, cleanUpFunc, err := generateFakePackerConfigData() - if err != nil { - t.Fatalf("error encountered while creating fake Packer configuration data %v", err) - } - defer cleanUpFunc() - - cfg := config{ - Plugins: &packer.PluginConfig{ - Builders: packer.MapOfBuilder{}, - PostProcessors: packer.MapOfPostProcessor{}, - Provisioners: packer.MapOfProvisioner{}, - }, - } - - if err := decodeConfig(strings.NewReader(packerConfigData), &cfg); err != nil { - t.Fatalf("error encountered decoding configuration: %v", err) - } - - /* Let's clear out any custom Builders or PostProcessors that were part of the config. - This step does not remove them from disk, it just removes them from of plugins Packer knows about. - */ - cfg.RawBuilders = nil - cfg.RawPostProcessors = nil - - cfg.LoadExternalComponentsFromConfig() - - if len(cfg.Plugins.Builders.List()) != 0 { - t.Errorf("loaded external builders when it wasn't supposed to; got %v as the resulting config", cfg.Plugins.Builders) - } - - if len(cfg.Plugins.PostProcessors.List()) != 0 { - t.Errorf("loaded external post-processors when it wasn't supposed to; got %v as the resulting config", cfg.Plugins.PostProcessors) - } - - if len(cfg.Plugins.Provisioners.List()) != 1 || !cfg.Plugins.Provisioners.Has("super-shell") { - t.Errorf("failed to load external provisioners; got %v as the resulting config", cfg.Plugins.Provisioners) - } -} - -func TestLoadSingleComponent(t *testing.T) { - - // .exe will work everyone for testing purpose, but mostly here to help Window's test runs. - tmpFile, err := os.CreateTemp(".", "packer-builder-*.exe") - if err != nil { - t.Fatalf("failed to create test file with error: %s", err) - } - defer os.Remove(tmpFile.Name()) - - tt := []struct { - pluginPath string - errorExpected bool - }{ - {pluginPath: tmpFile.Name(), errorExpected: false}, - {pluginPath: "./non-existing-file", errorExpected: true}, - } - - cfg := config{ - Plugins: &packer.PluginConfig{ - Builders: packer.MapOfBuilder{}, - PostProcessors: packer.MapOfPostProcessor{}, - Provisioners: packer.MapOfProvisioner{}, - }, - } - - for _, tc := range tt { - tc := tc - _, err := cfg.loadSingleComponent(tc.pluginPath) - if tc.errorExpected && err == nil { - t.Errorf("expected loadSingleComponent(%s) to error but it didn't", tc.pluginPath) - continue - } - - if err != nil && !tc.errorExpected { - t.Errorf("expected loadSingleComponent(%s) to load properly but got an error: %v", tc.pluginPath, err) - } - } - -} - -func generateFakePlugins(dirname string, pluginNames []string) (string, []string, func(), error) { - dir, err := os.MkdirTemp("", dirname) - if err != nil { - return "", nil, nil, fmt.Errorf("failed to create temporary test directory: %v", err) - } - - cleanUpFunc := func() { - os.RemoveAll(dir) - } - - var suffix string - if runtime.GOOS == "windows" { - suffix = ".exe" - } - - 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, 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, - "PluginMaxPort": 25, - "disable_checkpoint": true, - "disable_checkpoint_signature": true, - "builders": { - "cloud-xyz": %q - }, - "provisioners": { - "super-shell": %q - }, - "post-processors": { - "noop": %q - } - }`, plugins[0], plugins[1], plugins[2]) - - return -} diff --git a/main.go b/main.go index 81a65a68aab..6becbe49196 100644 --- a/main.go +++ b/main.go @@ -381,7 +381,9 @@ func loadConfig() (*config, error) { return nil, err } - config.LoadExternalComponentsFromConfig() + if err := config.LoadExternalComponentsFromConfig(); err != nil { + return nil, fmt.Errorf("%s: %s", configFilePath, err) + } return &config, nil }