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 }