github.com/pgray/terraform@v0.5.4-0.20170822184730-b6a464c5214d/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 continue 63 } 64 65 // New-style paths must have a version segment in filename 66 if strings.Contains(strings.ToLower(fullName), "_v") { 67 absPath, err := filepath.Abs(filepath.Join(dir, fullName)) 68 if err != nil { 69 log.Printf("[ERROR] plugin filepath error: %s", err) 70 continue 71 } 72 73 log.Printf("[DEBUG] found %s %q", kind, fullName) 74 ret = append(ret, filepath.Clean(absPath)) 75 continue 76 } 77 78 // Legacy style with files directly in the base directory 79 absPath, err := filepath.Abs(filepath.Join(dir, fullName)) 80 if err != nil { 81 log.Printf("[ERROR] plugin filepath error: %s", err) 82 continue 83 } 84 85 log.Printf("[WARNING] found legacy %s %q", kind, fullName) 86 87 ret = append(ret, filepath.Clean(absPath)) 88 } 89 } 90 91 return ret 92 } 93 94 // ResolvePluginPaths takes a list of paths to plugin executables (as returned 95 // by e.g. FindPluginPaths) and produces a PluginMetaSet describing the 96 // referenced plugins. 97 // 98 // If the same combination of plugin name and version appears multiple times, 99 // the earlier reference will be preferred. Several different versions of 100 // the same plugin name may be returned, in which case the methods of 101 // PluginMetaSet can be used to filter down. 102 func ResolvePluginPaths(paths []string) PluginMetaSet { 103 s := make(PluginMetaSet) 104 105 type nameVersion struct { 106 Name string 107 Version string 108 } 109 found := make(map[nameVersion]struct{}) 110 111 for _, path := range paths { 112 baseName := strings.ToLower(filepath.Base(path)) 113 if !strings.HasPrefix(baseName, "terraform-") { 114 // Should never happen with reasonable input 115 continue 116 } 117 118 baseName = baseName[10:] 119 firstDash := strings.Index(baseName, "-") 120 if firstDash == -1 { 121 // Should never happen with reasonable input 122 continue 123 } 124 125 baseName = baseName[firstDash+1:] 126 if baseName == "" { 127 // Should never happen with reasonable input 128 continue 129 } 130 131 // Trim the .exe suffix used on Windows before we start wrangling 132 // the remainder of the path. 133 if strings.HasSuffix(baseName, ".exe") { 134 baseName = baseName[:len(baseName)-4] 135 } 136 137 parts := strings.SplitN(baseName, "_v", 2) 138 name := parts[0] 139 version := VersionZero 140 if len(parts) == 2 { 141 version = parts[1] 142 } 143 144 // Auto-installed plugins contain an extra name portion representing 145 // the expected plugin version, which we must trim off. 146 if underX := strings.Index(version, "_x"); underX != -1 { 147 version = version[:underX] 148 } 149 150 if _, ok := found[nameVersion{name, version}]; ok { 151 // Skip duplicate versions of the same plugin 152 // (We do this during this step because after this we will be 153 // dealing with sets and thus lose our ordering with which to 154 // decide preference.) 155 continue 156 } 157 158 s.Add(PluginMeta{ 159 Name: name, 160 Version: VersionStr(version), 161 Path: path, 162 }) 163 found[nameVersion{name, version}] = struct{}{} 164 } 165 166 return s 167 }