mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
965c0f3f91
Running the tool this way ensures that we'll always run the version selected by our go.mod file, rather than whatever happened to be available in $GOPATH/bin on the system where we're running this. This change caused some contexts to now be using a newer version of staticcheck with additional checks, and so this commit also includes some changes to quiet the new warnings without any change in overall behavior.
190 lines
5.4 KiB
Go
190 lines
5.4 KiB
Go
package discovery
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// FindPlugins looks in the given directories for files whose filenames
|
|
// suggest that they are plugins of the given kind (e.g. "provider") and
|
|
// returns a PluginMetaSet representing the discovered potential-plugins.
|
|
//
|
|
// Currently this supports two different naming schemes. The current
|
|
// standard naming scheme is a subdirectory called $GOOS-$GOARCH containing
|
|
// files named terraform-$KIND-$NAME-V$VERSION. The legacy naming scheme is
|
|
// files directly in the given directory whose names are like
|
|
// terraform-$KIND-$NAME.
|
|
//
|
|
// Only one plugin will be returned for each unique plugin (name, version)
|
|
// pair, with preference given to files found in earlier directories.
|
|
//
|
|
// This is a convenience wrapper around FindPluginPaths and ResolvePluginsPaths.
|
|
func FindPlugins(kind string, dirs []string) PluginMetaSet {
|
|
return ResolvePluginPaths(FindPluginPaths(kind, dirs))
|
|
}
|
|
|
|
// FindPluginPaths looks in the given directories for files whose filenames
|
|
// suggest that they are plugins of the given kind (e.g. "provider").
|
|
//
|
|
// The return value is a list of absolute paths that appear to refer to
|
|
// plugins in the given directories, based only on what can be inferred
|
|
// from the naming scheme. The paths returned are ordered such that files
|
|
// in later dirs appear after files in earlier dirs in the given directory
|
|
// list. Within the same directory plugins are returned in a consistent but
|
|
// undefined order.
|
|
func FindPluginPaths(kind string, dirs []string) []string {
|
|
// This is just a thin wrapper around findPluginPaths so that we can
|
|
// use the latter in tests with a fake machineName so we can use our
|
|
// test fixtures.
|
|
return findPluginPaths(kind, dirs)
|
|
}
|
|
|
|
func findPluginPaths(kind string, dirs []string) []string {
|
|
prefix := "terraform-" + kind + "-"
|
|
|
|
ret := make([]string, 0, len(dirs))
|
|
|
|
for _, dir := range dirs {
|
|
items, err := ioutil.ReadDir(dir)
|
|
if err != nil {
|
|
// Ignore missing dirs, non-dirs, etc
|
|
continue
|
|
}
|
|
|
|
log.Printf("[DEBUG] checking for %s in %q", kind, dir)
|
|
|
|
for _, item := range items {
|
|
fullName := item.Name()
|
|
|
|
if !strings.HasPrefix(fullName, prefix) {
|
|
continue
|
|
}
|
|
|
|
// New-style paths must have a version segment in filename
|
|
if strings.Contains(strings.ToLower(fullName), "_v") {
|
|
absPath, err := filepath.Abs(filepath.Join(dir, fullName))
|
|
if err != nil {
|
|
log.Printf("[ERROR] plugin filepath error: %s", err)
|
|
continue
|
|
}
|
|
|
|
// Check that the file we found is usable
|
|
if !pathIsFile(absPath) {
|
|
log.Printf("[ERROR] ignoring non-file %s", absPath)
|
|
continue
|
|
}
|
|
|
|
log.Printf("[DEBUG] found %s %q", kind, fullName)
|
|
ret = append(ret, filepath.Clean(absPath))
|
|
continue
|
|
}
|
|
|
|
// Legacy style with files directly in the base directory
|
|
absPath, err := filepath.Abs(filepath.Join(dir, fullName))
|
|
if err != nil {
|
|
log.Printf("[ERROR] plugin filepath error: %s", err)
|
|
continue
|
|
}
|
|
|
|
// Check that the file we found is usable
|
|
if !pathIsFile(absPath) {
|
|
log.Printf("[ERROR] ignoring non-file %s", absPath)
|
|
continue
|
|
}
|
|
|
|
log.Printf("[WARN] found legacy %s %q", kind, fullName)
|
|
|
|
ret = append(ret, filepath.Clean(absPath))
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// Returns true if and only if the given path refers to a file or a symlink
|
|
// to a file.
|
|
func pathIsFile(path string) bool {
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return !info.IsDir()
|
|
}
|
|
|
|
// ResolvePluginPaths takes a list of paths to plugin executables (as returned
|
|
// by e.g. FindPluginPaths) and produces a PluginMetaSet describing the
|
|
// referenced plugins.
|
|
//
|
|
// If the same combination of plugin name and version appears multiple times,
|
|
// the earlier reference will be preferred. Several different versions of
|
|
// the same plugin name may be returned, in which case the methods of
|
|
// PluginMetaSet can be used to filter down.
|
|
func ResolvePluginPaths(paths []string) PluginMetaSet {
|
|
s := make(PluginMetaSet)
|
|
|
|
type nameVersion struct {
|
|
Name string
|
|
Version string
|
|
}
|
|
found := make(map[nameVersion]struct{})
|
|
|
|
for _, path := range paths {
|
|
baseName := strings.ToLower(filepath.Base(path))
|
|
if !strings.HasPrefix(baseName, "terraform-") {
|
|
// Should never happen with reasonable input
|
|
continue
|
|
}
|
|
|
|
baseName = baseName[10:]
|
|
firstDash := strings.Index(baseName, "-")
|
|
if firstDash == -1 {
|
|
// Should never happen with reasonable input
|
|
continue
|
|
}
|
|
|
|
baseName = baseName[firstDash+1:]
|
|
if baseName == "" {
|
|
// Should never happen with reasonable input
|
|
continue
|
|
}
|
|
|
|
// Trim the .exe suffix used on Windows before we start wrangling
|
|
// the remainder of the path.
|
|
baseName = strings.TrimSuffix(baseName, ".exe")
|
|
|
|
parts := strings.SplitN(baseName, "_v", 2)
|
|
name := parts[0]
|
|
version := VersionZero
|
|
if len(parts) == 2 {
|
|
version = parts[1]
|
|
}
|
|
|
|
// Auto-installed plugins contain an extra name portion representing
|
|
// the expected plugin version, which we must trim off.
|
|
if underX := strings.Index(version, "_x"); underX != -1 {
|
|
version = version[:underX]
|
|
}
|
|
|
|
if _, ok := found[nameVersion{name, version}]; ok {
|
|
// Skip duplicate versions of the same plugin
|
|
// (We do this during this step because after this we will be
|
|
// dealing with sets and thus lose our ordering with which to
|
|
// decide preference.)
|
|
continue
|
|
}
|
|
|
|
s.Add(PluginMeta{
|
|
Name: name,
|
|
Version: VersionStr(version),
|
|
Path: path,
|
|
})
|
|
found[nameVersion{name, version}] = struct{}{}
|
|
}
|
|
|
|
return s
|
|
}
|