github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/plugin/discovery/find.go (about)

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