github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/config.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strings"
    13  
    14  	"github.com/hashicorp/packer/command"
    15  	"github.com/hashicorp/packer/packer"
    16  	"github.com/hashicorp/packer/packer/plugin"
    17  	"github.com/kardianos/osext"
    18  )
    19  
    20  // PACKERSPACE is used to represent the spaces that separate args for a command
    21  // without being confused with spaces in the path to the command itself.
    22  const PACKERSPACE = "-PACKERSPACE-"
    23  
    24  type config struct {
    25  	DisableCheckpoint          bool `json:"disable_checkpoint"`
    26  	DisableCheckpointSignature bool `json:"disable_checkpoint_signature"`
    27  	PluginMinPort              uint
    28  	PluginMaxPort              uint
    29  
    30  	Builders       map[string]string
    31  	PostProcessors map[string]string `json:"post-processors"`
    32  	Provisioners   map[string]string
    33  }
    34  
    35  // Decodes configuration in JSON format from the given io.Reader into
    36  // the config object pointed to.
    37  func decodeConfig(r io.Reader, c *config) error {
    38  	decoder := json.NewDecoder(r)
    39  	return decoder.Decode(c)
    40  }
    41  
    42  // Discover discovers plugins.
    43  //
    44  // Search the directory of the executable, then the plugins directory, and
    45  // finally the CWD, in that order. Any conflicts will overwrite previously
    46  // found plugins, in that order.
    47  // Hence, the priority order is the reverse of the search order - i.e., the
    48  // CWD has the highest priority.
    49  func (c *config) Discover() error {
    50  	// If we are already inside a plugin process we should not need to
    51  	// discover anything.
    52  	if os.Getenv(plugin.MagicCookieKey) == plugin.MagicCookieValue {
    53  		return nil
    54  	}
    55  
    56  	// First, look in the same directory as the executable.
    57  	exePath, err := osext.Executable()
    58  	if err != nil {
    59  		log.Printf("[ERR] Error loading exe directory: %s", err)
    60  	} else {
    61  		if err := c.discover(filepath.Dir(exePath)); err != nil {
    62  			return err
    63  		}
    64  	}
    65  
    66  	// Next, look in the plugins directory.
    67  	dir, err := packer.ConfigDir()
    68  	if err != nil {
    69  		log.Printf("[ERR] Error loading config directory: %s", err)
    70  	} else {
    71  		if err := c.discover(filepath.Join(dir, "plugins")); err != nil {
    72  			return err
    73  		}
    74  	}
    75  
    76  	// Next, look in the CWD.
    77  	if err := c.discover("."); err != nil {
    78  		return err
    79  	}
    80  
    81  	// Finally, try to use an internal plugin. Note that this will not override
    82  	// any previously-loaded plugins.
    83  	if err := c.discoverInternal(); err != nil {
    84  		return err
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  // This is a proper packer.BuilderFunc that can be used to load packer.Builder
    91  // implementations from the defined plugins.
    92  func (c *config) LoadBuilder(name string) (packer.Builder, error) {
    93  	log.Printf("Loading builder: %s\n", name)
    94  	bin, ok := c.Builders[name]
    95  	if !ok {
    96  		log.Printf("Builder not found: %s\n", name)
    97  		return nil, nil
    98  	}
    99  
   100  	return c.pluginClient(bin).Builder()
   101  }
   102  
   103  // This is a proper implementation of packer.HookFunc that can be used
   104  // to load packer.Hook implementations from the defined plugins.
   105  func (c *config) LoadHook(name string) (packer.Hook, error) {
   106  	log.Printf("Loading hook: %s\n", name)
   107  	return c.pluginClient(name).Hook()
   108  }
   109  
   110  // This is a proper packer.PostProcessorFunc that can be used to load
   111  // packer.PostProcessor implementations from defined plugins.
   112  func (c *config) LoadPostProcessor(name string) (packer.PostProcessor, error) {
   113  	log.Printf("Loading post-processor: %s", name)
   114  	bin, ok := c.PostProcessors[name]
   115  	if !ok {
   116  		log.Printf("Post-processor not found: %s", name)
   117  		return nil, nil
   118  	}
   119  
   120  	return c.pluginClient(bin).PostProcessor()
   121  }
   122  
   123  // This is a proper packer.ProvisionerFunc that can be used to load
   124  // packer.Provisioner implementations from defined plugins.
   125  func (c *config) LoadProvisioner(name string) (packer.Provisioner, error) {
   126  	log.Printf("Loading provisioner: %s\n", name)
   127  	bin, ok := c.Provisioners[name]
   128  	if !ok {
   129  		log.Printf("Provisioner not found: %s\n", name)
   130  		return nil, nil
   131  	}
   132  
   133  	return c.pluginClient(bin).Provisioner()
   134  }
   135  
   136  func (c *config) discover(path string) error {
   137  	var err error
   138  
   139  	if !filepath.IsAbs(path) {
   140  		path, err = filepath.Abs(path)
   141  		if err != nil {
   142  			return err
   143  		}
   144  	}
   145  
   146  	err = c.discoverSingle(
   147  		filepath.Join(path, "packer-builder-*"), &c.Builders)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	err = c.discoverSingle(
   153  		filepath.Join(path, "packer-post-processor-*"), &c.PostProcessors)
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	return c.discoverSingle(
   159  		filepath.Join(path, "packer-provisioner-*"), &c.Provisioners)
   160  }
   161  
   162  func (c *config) discoverSingle(glob string, m *map[string]string) error {
   163  	matches, err := filepath.Glob(glob)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	if *m == nil {
   169  		*m = make(map[string]string)
   170  	}
   171  
   172  	prefix := filepath.Base(glob)
   173  	prefix = prefix[:strings.Index(prefix, "*")]
   174  	for _, match := range matches {
   175  		file := filepath.Base(match)
   176  
   177  		// One Windows, ignore any plugins that don't end in .exe.
   178  		// We could do a full PATHEXT parse, but this is probably good enough.
   179  		if runtime.GOOS == "windows" && strings.ToLower(filepath.Ext(file)) != ".exe" {
   180  			log.Printf(
   181  				"[DEBUG] Ignoring plugin match %s, no exe extension",
   182  				match)
   183  			continue
   184  		}
   185  
   186  		// If the filename has a ".", trim up to there
   187  		if idx := strings.Index(file, "."); idx >= 0 {
   188  			file = file[:idx]
   189  		}
   190  
   191  		// Look for foo-bar-baz. The plugin name is "baz"
   192  		plugin := file[len(prefix):]
   193  		log.Printf("[DEBUG] Discovered plugin: %s = %s", plugin, match)
   194  		(*m)[plugin] = match
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  func (c *config) discoverInternal() error {
   201  	// Get the packer binary path
   202  	packerPath, err := osext.Executable()
   203  	if err != nil {
   204  		log.Printf("[ERR] Error loading exe directory: %s", err)
   205  		return err
   206  	}
   207  
   208  	for builder := range command.Builders {
   209  		_, found := (c.Builders)[builder]
   210  		if !found {
   211  			log.Printf("Using internal plugin for %s", builder)
   212  			(c.Builders)[builder] = fmt.Sprintf("%s%splugin%spacker-builder-%s",
   213  				packerPath, PACKERSPACE, PACKERSPACE, builder)
   214  		}
   215  	}
   216  
   217  	for provisioner := range command.Provisioners {
   218  		_, found := (c.Provisioners)[provisioner]
   219  		if !found {
   220  			log.Printf("Using internal plugin for %s", provisioner)
   221  			(c.Provisioners)[provisioner] = fmt.Sprintf(
   222  				"%s%splugin%spacker-provisioner-%s",
   223  				packerPath, PACKERSPACE, PACKERSPACE, provisioner)
   224  		}
   225  	}
   226  
   227  	for postProcessor := range command.PostProcessors {
   228  		_, found := (c.PostProcessors)[postProcessor]
   229  		if !found {
   230  			log.Printf("Using internal plugin for %s", postProcessor)
   231  			(c.PostProcessors)[postProcessor] = fmt.Sprintf(
   232  				"%s%splugin%spacker-post-processor-%s",
   233  				packerPath, PACKERSPACE, PACKERSPACE, postProcessor)
   234  		}
   235  	}
   236  
   237  	return nil
   238  }
   239  
   240  func (c *config) pluginClient(path string) *plugin.Client {
   241  	originalPath := path
   242  
   243  	// First attempt to find the executable by consulting the PATH.
   244  	path, err := exec.LookPath(path)
   245  	if err != nil {
   246  		// If that doesn't work, look for it in the same directory
   247  		// as the `packer` executable (us).
   248  		log.Printf("Plugin could not be found. Checking same directory as executable.")
   249  		exePath, err := osext.Executable()
   250  		if err != nil {
   251  			log.Printf("Couldn't get current exe path: %s", err)
   252  		} else {
   253  			log.Printf("Current exe path: %s", exePath)
   254  			path = filepath.Join(filepath.Dir(exePath), filepath.Base(originalPath))
   255  		}
   256  	}
   257  
   258  	// Check for special case using `packer plugin PLUGIN`
   259  	args := []string{}
   260  	if strings.Contains(path, PACKERSPACE) {
   261  		parts := strings.Split(path, PACKERSPACE)
   262  		path = parts[0]
   263  		args = parts[1:]
   264  	}
   265  
   266  	// If everything failed, just use the original path and let the error
   267  	// bubble through.
   268  	if path == "" {
   269  		path = originalPath
   270  	}
   271  
   272  	log.Printf("Creating plugin client for path: %s", path)
   273  	var config plugin.ClientConfig
   274  	config.Cmd = exec.Command(path, args...)
   275  	config.Managed = true
   276  	config.MinPort = c.PluginMinPort
   277  	config.MaxPort = c.PluginMaxPort
   278  	return plugin.NewClient(&config)
   279  }