github.com/victortrac/packer@v0.7.6-0.20160602180447-63c7fdb6e41f/config.go (about)

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