config: Fix loading external plugins from a packerconfig
This change introduces a loadExternalComponent which can be used for loading a single plugin path. The function is a combination of the discoverSingle and discoverExternalComponents functions.
This commit is contained in:
parent
0e177915f8
commit
fb76323c4d
81
config.go
81
config.go
|
@ -27,19 +27,88 @@ type config struct {
|
|||
DisableCheckpointSignature bool `json:"disable_checkpoint_signature"`
|
||||
PluginMinPort int
|
||||
PluginMaxPort int
|
||||
|
||||
Builders packer.MapOfBuilder
|
||||
Provisioners packer.MapOfProvisioner
|
||||
PostProcessors packer.MapOfPostProcessor `json:"post-processors"`
|
||||
RawBuilders map[string]string `json:"builders"`
|
||||
RawProvisioners map[string]string `json:"provisioners"`
|
||||
RawPostProcessors map[string]string `json:"post-processors"`
|
||||
Builders packer.MapOfBuilder `json:"-"`
|
||||
Provisioners packer.MapOfProvisioner `json:"-"`
|
||||
PostProcessors packer.MapOfPostProcessor `json:"-"`
|
||||
}
|
||||
|
||||
// Decodes configuration in JSON format from the given io.Reader into
|
||||
// decodeConfig decodes configuration in JSON format from the given io.Reader into
|
||||
// the config object pointed to.
|
||||
func decodeConfig(r io.Reader, c *config) error {
|
||||
decoder := json.NewDecoder(r)
|
||||
return decoder.Decode(c)
|
||||
}
|
||||
|
||||
// LoadExternalComponentsFromConfig loads plugins defined in RawBuilders, RawProvisioners, and RawPostProcessors.
|
||||
func (c *config) LoadExternalComponentsFromConfig() {
|
||||
// helper to build up list of plugin paths
|
||||
extractPaths := func(m map[string]string) []string {
|
||||
paths := make([]string, 0, len(m))
|
||||
for _, v := range m {
|
||||
paths = append(paths, v)
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
var pluginPaths []string
|
||||
pluginPaths = append(pluginPaths, extractPaths(c.RawProvisioners)...)
|
||||
pluginPaths = append(pluginPaths, extractPaths(c.RawBuilders)...)
|
||||
pluginPaths = append(pluginPaths, extractPaths(c.RawPostProcessors)...)
|
||||
|
||||
var externallyUsed = make([]string, 0, len(pluginPaths))
|
||||
for _, pluginPath := range pluginPaths {
|
||||
if name, ok := c.loadExternalComponent(pluginPath); ok {
|
||||
log.Printf("[DEBUG] 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) loadExternalComponent(path string) (string, bool) {
|
||||
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" {
|
||||
log.Printf("[DEBUG] Ignoring plugin %s, no exe extension", path)
|
||||
return "", false
|
||||
}
|
||||
|
||||
// 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.Builders[pluginName] = func() (packer.Builder, error) {
|
||||
return c.pluginClient(path).Builder()
|
||||
}
|
||||
case strings.HasPrefix(pluginName, "packer-post-processor-"):
|
||||
pluginName = pluginName[len("packer-post-processor-"):]
|
||||
c.PostProcessors[pluginName] = func() (packer.PostProcessor, error) {
|
||||
return c.pluginClient(path).PostProcessor()
|
||||
}
|
||||
case strings.HasPrefix(pluginName, "packer-provisioner-"):
|
||||
pluginName = pluginName[len("packer-provisioner-"):]
|
||||
c.Provisioners[pluginName] = func() (packer.Provisioner, error) {
|
||||
return c.pluginClient(path).Provisioner()
|
||||
}
|
||||
}
|
||||
|
||||
return pluginName, true
|
||||
}
|
||||
|
||||
// Discover discovers plugins.
|
||||
//
|
||||
// Search the directory of the executable, then the plugins directory, and
|
||||
|
@ -194,7 +263,7 @@ func (c *config) discoverSingle(glob string) (map[string]string, error) {
|
|||
for _, match := range matches {
|
||||
file := filepath.Base(match)
|
||||
|
||||
// One Windows, ignore any plugins that don't end in .exe.
|
||||
// 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(file)) != ".exe" {
|
||||
log.Printf(
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestDecodeConfig(t *testing.T) {
|
||||
|
||||
packerConfig := `
|
||||
{
|
||||
"PluginMinPort": 10,
|
||||
"PluginMaxPort": 25,
|
||||
"disable_checkpoint": true,
|
||||
"disable_checkpoint_signature": true,
|
||||
"provisioners": {
|
||||
"super-shell": "packer-provisioner-super-shell"
|
||||
}
|
||||
}`
|
||||
|
||||
var cfg config
|
||||
err := decodeConfig(strings.NewReader(packerConfig), &cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("error encountered decoding configuration: %v", err)
|
||||
}
|
||||
|
||||
var expectedCfg config
|
||||
json.NewDecoder(strings.NewReader(packerConfig)).Decode(&expectedCfg)
|
||||
if !reflect.DeepEqual(cfg, expectedCfg) {
|
||||
t.Errorf("failed to load custom configuration data; expected %v got %v", expectedCfg, cfg)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
var cfg config
|
||||
cfg.Builders = packer.MapOfBuilder{}
|
||||
cfg.PostProcessors = packer.MapOfPostProcessor{}
|
||||
cfg.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.Builders) != 1 || !cfg.Builders.Has("cloud-xyz") {
|
||||
t.Errorf("failed to load external builders; got %v as the resulting config", cfg.Builders)
|
||||
}
|
||||
|
||||
if len(cfg.Provisioners) != 1 || !cfg.Provisioners.Has("super-shell") {
|
||||
t.Errorf("failed to load external provisioners; got %v as the resulting config", cfg.Provisioners)
|
||||
}
|
||||
|
||||
if len(cfg.PostProcessors) != 1 || !cfg.PostProcessors.Has("noop") {
|
||||
t.Errorf("failed to load external post-processors; got %v as the resulting config", cfg.PostProcessors)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
var cfg config
|
||||
cfg.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.Builders) != 0 || cfg.Builders.Has("cloud-xyz") {
|
||||
t.Errorf("loaded external builders when it wasn't supposed to; got %v as the resulting config", cfg.Builders)
|
||||
}
|
||||
|
||||
if len(cfg.Provisioners) != 1 || !cfg.Provisioners.Has("super-shell") {
|
||||
t.Errorf("failed to load external provisioners; got %v as the resulting config", cfg.Provisioners)
|
||||
}
|
||||
|
||||
if len(cfg.PostProcessors) != 0 || cfg.PostProcessors.Has("noop") {
|
||||
t.Errorf("loaded external post-processors when it wasn't supposed to; got %v as the resulting config", cfg.PostProcessors)
|
||||
}
|
||||
}
|
||||
|
||||
/* 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")
|
||||
if err != nil {
|
||||
return "", 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 := [...]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)
|
||||
if err != nil {
|
||||
cleanUpFunc()
|
||||
return "", nil, fmt.Errorf("failed to create temporary plugin file (%s): %v", plugin, 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
|
||||
}
|
13
main.go
13
main.go
|
@ -299,13 +299,14 @@ func loadConfig() (*config, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// start by loading from PACKER_CONFIG if available
|
||||
log.Print("Checking 'PACKER_CONFIG' for a config file path")
|
||||
configFilePath := os.Getenv("PACKER_CONFIG")
|
||||
if configFilePath != "" {
|
||||
log.Printf("'PACKER_CONFIG' set, loading config from environment.")
|
||||
} else {
|
||||
var err error
|
||||
configFilePath, err = packer.ConfigFile()
|
||||
|
||||
if configFilePath == "" {
|
||||
var err error
|
||||
log.Print("'PACKER_CONFIG' not set; checking the default config file path")
|
||||
configFilePath, err = packer.ConfigFile()
|
||||
if err != nil {
|
||||
log.Printf("Error detecting default config file path: %s", err)
|
||||
}
|
||||
|
@ -331,6 +332,8 @@ func loadConfig() (*config, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
config.LoadExternalComponentsFromConfig()
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue