github.com/sl1pm4t/terraform@v0.6.4-0.20170725213156-870617d22df3/plugin/discovery/find.go (about)

     1  package discovery
     2  
     3  import (
     4  	"io/ioutil"
     5  	"log"
     6  	"path/filepath"
     7  	"strings"
     8  )
     9  
    10  // FindPlugins looks in the given directories for files whose filenames
    11  // suggest that they are plugins of the given kind (e.g. "provider") and
    12  // returns a PluginMetaSet representing the discovered potential-plugins.
    13  //
    14  // Currently this supports two different naming schemes. The current
    15  // standard naming scheme is a subdirectory called $GOOS-$GOARCH containing
    16  // files named terraform-$KIND-$NAME-V$VERSION. The legacy naming scheme is
    17  // files directly in the given directory whose names are like
    18  // terraform-$KIND-$NAME.
    19  //
    20  // Only one plugin will be returned for each unique plugin (name, version)
    21  // pair, with preference given to files found in earlier directories.
    22  //
    23  // This is a convenience wrapper around FindPluginPaths and ResolvePluginsPaths.
    24  func FindPlugins(kind string, dirs []string) PluginMetaSet {
    25  	return ResolvePluginPaths(FindPluginPaths(kind, dirs))
    26  }
    27  
    28  // FindPluginPaths looks in the given directories for files whose filenames
    29  // suggest that they are plugins of the given kind (e.g. "provider").
    30  //
    31  // The return value is a list of absolute paths that appear to refer to
    32  // plugins in the given directories, based only on what can be inferred
    33  // from the naming scheme. The paths returned are ordered such that files
    34  // in later dirs appear after files in earlier dirs in the given directory
    35  // list. Within the same directory plugins are returned in a consistent but
    36  // undefined order.
    37  func FindPluginPaths(kind string, dirs []string) []string {
    38  	// This is just a thin wrapper around findPluginPaths so that we can
    39  	// use the latter in tests with a fake machineName so we can use our
    40  	// test fixtures.
    41  	return findPluginPaths(kind, dirs)
    42  }
    43  
    44  func findPluginPaths(kind string, dirs []string) []string {
    45  	prefix := "terraform-" + kind + "-"
    46  
    47  	ret := make([]string, 0, len(dirs))
    48  
    49  	for _, dir := range dirs {
    50  		items, err := ioutil.ReadDir(dir)
    51  		if err != nil {
    52  			// Ignore missing dirs, non-dirs, etc
    53  			continue
    54  		}
    55  
    56  		log.Printf("[DEBUG] checking for %s in %q", kind, dir)
    57  
    58  		for _, item := range items {
    59  			fullName := item.Name()
    60  
    61  			if !strings.HasPrefix(fullName, prefix) {
    62  				log.Printf("[DEBUG] skipping %q, not a %s", fullName, kind)
    63  				continue
    64  			}
    65  
    66  			// New-style paths must have a version segment in filename
    67  			if strings.Contains(strings.ToLower(fullName), "_v") {
    68  				absPath, err := filepath.Abs(filepath.Join(dir, fullName))
    69  				if err != nil {
    70  					log.Printf("[ERROR] plugin filepath error: %s", err)
    71  					continue
    72  				}
    73  
    74  				log.Printf("[DEBUG] found %s %q", kind, fullName)
    75  				ret = append(ret, filepath.Clean(absPath))
    76  				continue
    77  			}
    78  
    79  			// Legacy style with files directly in the base directory
    80  			absPath, err := filepath.Abs(filepath.Join(dir, fullName))
    81  			if err != nil {
    82  				log.Printf("[ERROR] plugin filepath error: %s", err)
    83  				continue
    84  			}
    85  
    86  			log.Printf("[WARNING] found legacy %s %q", kind, fullName)
    87  
    88  			ret = append(ret, filepath.Clean(absPath))
    89  		}
    90  	}
    91  
    92  	return ret
    93  }
    94  
    95  // ResolvePluginPaths takes a list of paths to plugin executables (as returned
    96  // by e.g. FindPluginPaths) and produces a PluginMetaSet describing the
    97  // referenced plugins.
    98  //
    99  // If the same combination of plugin name and version appears multiple times,
   100  // the earlier reference will be preferred. Several different versions of
   101  // the same plugin name may be returned, in which case the methods of
   102  // PluginMetaSet can be used to filter down.
   103  func ResolvePluginPaths(paths []string) PluginMetaSet {
   104  	s := make(PluginMetaSet)
   105  
   106  	type nameVersion struct {
   107  		Name    string
   108  		Version string
   109  	}
   110  	found := make(map[nameVersion]struct{})
   111  
   112  	for _, path := range paths {
   113  		baseName := strings.ToLower(filepath.Base(path))
   114  		if !strings.HasPrefix(baseName, "terraform-") {
   115  			// Should never happen with reasonable input
   116  			continue
   117  		}
   118  
   119  		baseName = baseName[10:]
   120  		firstDash := strings.Index(baseName, "-")
   121  		if firstDash == -1 {
   122  			// Should never happen with reasonable input
   123  			continue
   124  		}
   125  
   126  		baseName = baseName[firstDash+1:]
   127  		if baseName == "" {
   128  			// Should never happen with reasonable input
   129  			continue
   130  		}
   131  
   132  		// Trim the .exe suffix used on Windows before we start wrangling
   133  		// the remainder of the path.
   134  		if strings.HasSuffix(baseName, ".exe") {
   135  			baseName = baseName[:len(baseName)-4]
   136  		}
   137  
   138  		parts := strings.SplitN(baseName, "_v", 2)
   139  		name := parts[0]
   140  		version := VersionZero
   141  		if len(parts) == 2 {
   142  			version = parts[1]
   143  		}
   144  
   145  		// Auto-installed plugins contain an extra name portion representing
   146  		// the expected plugin version, which we must trim off.
   147  		if underX := strings.Index(version, "_x"); underX != -1 {
   148  			version = version[:underX]
   149  		}
   150  
   151  		if _, ok := found[nameVersion{name, version}]; ok {
   152  			// Skip duplicate versions of the same plugin
   153  			// (We do this during this step because after this we will be
   154  			// dealing with sets and thus lose our ordering with which to
   155  			// decide preference.)
   156  			continue
   157  		}
   158  
   159  		s.Add(PluginMeta{
   160  			Name:    name,
   161  			Version: VersionStr(version),
   162  			Path:    path,
   163  		})
   164  		found[nameVersion{name, version}] = struct{}{}
   165  	}
   166  
   167  	return s
   168  }