github.com/jhixson74/hashicorp-terraform@v0.11.12-beta1/plugin/discovery/find.go (about)

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