config: rm mono-component support from config file (#12998)

The global `PACKER_CONFIG` config file was already deprecated from
Packer core, but now with 1.11.0 since we remove support for
mono-component plugins, we are also removing the capability for that
config file to declare them.

Instead of silently not using those, Packer will now error with a
message pointing to the web docs on how to manage their plugins with the
updated workflows for Packer 1.11 and above.
This commit is contained in:
Lucas Bajolet 2024-05-30 08:25:08 -04:00 committed by GitHub
parent accbe97e1e
commit 8d4a9d66ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 14 additions and 244 deletions

View file

@ -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(pluginPaths) == 0 {
return nil
}
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)
componentList := &strings.Builder{}
for _, path := range pluginPaths {
fmt.Fprintf(componentList, "- %s\n", 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]
}
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()
})
}
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

View file

@ -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
}

View file

@ -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
}