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 }