github.com/emate/packer@v0.8.1-0.20150625195101-fe0fde195dc6/config.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"io"
     6  	"log"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  
    12  	"github.com/mitchellh/osext"
    13  	"github.com/mitchellh/packer/packer"
    14  	"github.com/mitchellh/packer/packer/plugin"
    15  )
    16  
    17  type config struct {
    18  	DisableCheckpoint          bool `json:"disable_checkpoint"`
    19  	DisableCheckpointSignature bool `json:"disable_checkpoint_signature"`
    20  	PluginMinPort              uint
    21  	PluginMaxPort              uint
    22  
    23  	Builders       map[string]string
    24  	PostProcessors map[string]string `json:"post-processors"`
    25  	Provisioners   map[string]string
    26  }
    27  
    28  // ConfigFile returns the default path to the configuration file. On
    29  // Unix-like systems this is the ".packerconfig" file in the home directory.
    30  // On Windows, this is the "packer.config" file in the application data
    31  // directory.
    32  func ConfigFile() (string, error) {
    33  	return configFile()
    34  }
    35  
    36  // ConfigDir returns the configuration directory for Packer.
    37  func ConfigDir() (string, error) {
    38  	return configDir()
    39  }
    40  
    41  // Decodes configuration in JSON format from the given io.Reader into
    42  // the config object pointed to.
    43  func decodeConfig(r io.Reader, c *config) error {
    44  	decoder := json.NewDecoder(r)
    45  	return decoder.Decode(c)
    46  }
    47  
    48  // Discover discovers plugins.
    49  //
    50  // Search the directory of the executable, then the plugins directory, and
    51  // finally the CWD, in that order. Any conflicts will overwrite previously
    52  // found plugins, in that order.
    53  // Hence, the priority order is the reverse of the search order - i.e., the
    54  // CWD has the highest priority.
    55  func (c *config) Discover() error {
    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 := 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  	// Last, look in the CWD.
    77  	if err := c.discover("."); err != nil {
    78  		return err
    79  	}
    80  
    81  	return nil
    82  }
    83  
    84  // This is a proper packer.BuilderFunc that can be used to load packer.Builder
    85  // implementations from the defined plugins.
    86  func (c *config) LoadBuilder(name string) (packer.Builder, error) {
    87  	log.Printf("Loading builder: %s\n", name)
    88  	bin, ok := c.Builders[name]
    89  	if !ok {
    90  		log.Printf("Builder not found: %s\n", name)
    91  		return nil, nil
    92  	}
    93  
    94  	return c.pluginClient(bin).Builder()
    95  }
    96  
    97  // This is a proper implementation of packer.HookFunc that can be used
    98  // to load packer.Hook implementations from the defined plugins.
    99  func (c *config) LoadHook(name string) (packer.Hook, error) {
   100  	log.Printf("Loading hook: %s\n", name)
   101  	return c.pluginClient(name).Hook()
   102  }
   103  
   104  // This is a proper packer.PostProcessorFunc that can be used to load
   105  // packer.PostProcessor implementations from defined plugins.
   106  func (c *config) LoadPostProcessor(name string) (packer.PostProcessor, error) {
   107  	log.Printf("Loading post-processor: %s", name)
   108  	bin, ok := c.PostProcessors[name]
   109  	if !ok {
   110  		log.Printf("Post-processor not found: %s", name)
   111  		return nil, nil
   112  	}
   113  
   114  	return c.pluginClient(bin).PostProcessor()
   115  }
   116  
   117  // This is a proper packer.ProvisionerFunc that can be used to load
   118  // packer.Provisioner implementations from defined plugins.
   119  func (c *config) LoadProvisioner(name string) (packer.Provisioner, error) {
   120  	log.Printf("Loading provisioner: %s\n", name)
   121  	bin, ok := c.Provisioners[name]
   122  	if !ok {
   123  		log.Printf("Provisioner not found: %s\n", name)
   124  		return nil, nil
   125  	}
   126  
   127  	return c.pluginClient(bin).Provisioner()
   128  }
   129  
   130  func (c *config) discover(path string) error {
   131  	var err error
   132  
   133  	if !filepath.IsAbs(path) {
   134  		path, err = filepath.Abs(path)
   135  		if err != nil {
   136  			return err
   137  		}
   138  	}
   139  
   140  	err = c.discoverSingle(
   141  		filepath.Join(path, "packer-builder-*"), &c.Builders)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	err = c.discoverSingle(
   147  		filepath.Join(path, "packer-post-processor-*"), &c.PostProcessors)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	err = c.discoverSingle(
   153  		filepath.Join(path, "packer-provisioner-*"), &c.Provisioners)
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  func (c *config) discoverSingle(glob string, m *map[string]string) error {
   162  	matches, err := filepath.Glob(glob)
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	if *m == nil {
   168  		*m = make(map[string]string)
   169  	}
   170  
   171  	prefix := filepath.Base(glob)
   172  	prefix = prefix[:strings.Index(prefix, "*")]
   173  	for _, match := range matches {
   174  		file := filepath.Base(match)
   175  
   176  		// One Windows, ignore any plugins that don't end in .exe.
   177  		// We could do a full PATHEXT parse, but this is probably good enough.
   178  		if runtime.GOOS == "windows" && strings.ToLower(filepath.Ext(file)) != ".exe" {
   179  			log.Printf(
   180  				"[DEBUG] Ignoring plugin match %s, no exe extension",
   181  				match)
   182  			continue
   183  		}
   184  
   185  		// If the filename has a ".", trim up to there
   186  		if idx := strings.Index(file, "."); idx >= 0 {
   187  			file = file[:idx]
   188  		}
   189  
   190  		// Look for foo-bar-baz. The plugin name is "baz"
   191  		plugin := file[len(prefix):]
   192  		log.Printf("[DEBUG] Discovered plugin: %s = %s", plugin, match)
   193  		(*m)[plugin] = match
   194  	}
   195  
   196  	return nil
   197  }
   198  
   199  func (c *config) pluginClient(path string) *plugin.Client {
   200  	originalPath := path
   201  
   202  	// First attempt to find the executable by consulting the PATH.
   203  	path, err := exec.LookPath(path)
   204  	if err != nil {
   205  		// If that doesn't work, look for it in the same directory
   206  		// as the `packer` executable (us).
   207  		log.Printf("Plugin could not be found. Checking same directory as executable.")
   208  		exePath, err := osext.Executable()
   209  		if err != nil {
   210  			log.Printf("Couldn't get current exe path: %s", err)
   211  		} else {
   212  			log.Printf("Current exe path: %s", exePath)
   213  			path = filepath.Join(filepath.Dir(exePath), filepath.Base(originalPath))
   214  		}
   215  	}
   216  
   217  	// If everything failed, just use the original path and let the error
   218  	// bubble through.
   219  	if path == "" {
   220  		path = originalPath
   221  	}
   222  
   223  	log.Printf("Creating plugin client for path: %s", path)
   224  	var config plugin.ClientConfig
   225  	config.Cmd = exec.Command(path)
   226  	config.Managed = true
   227  	config.MinPort = c.PluginMinPort
   228  	config.MaxPort = c.PluginMaxPort
   229  	return plugin.NewClient(&config)
   230  }