Skip to content

Commit

Permalink
config: rm mono-component support from config file (#12998)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lbajolet-hashicorp authored May 30, 2024
1 parent accbe97 commit 8d4a9d6
Show file tree
Hide file tree
Showing 3 changed files with 14 additions and 244 deletions.
66 changes: 11 additions & 55 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import (
"io"
"log"
"os"
"path/filepath"
"runtime"
"sort"
"strings"

packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
Expand Down Expand Up @@ -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))
Expand All @@ -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
Expand Down
188 changes: 0 additions & 188 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
4 changes: 3 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 8d4a9d6

Please sign in to comment.