github.com/pulumi/terraform@v1.4.0/pkg/providercache/dir.go (about)

     1  package providercache
     2  
     3  import (
     4  	"log"
     5  	"path/filepath"
     6  	"sort"
     7  
     8  	"github.com/pulumi/terraform/pkg/addrs"
     9  	"github.com/pulumi/terraform/pkg/getproviders"
    10  )
    11  
    12  // Dir represents a single local filesystem directory containing cached
    13  // provider plugin packages that can be both read from (to find providers to
    14  // use for operations) and written to (during provider installation).
    15  //
    16  // The contents of a cache directory follow the same naming conventions as a
    17  // getproviders.FilesystemMirrorSource, except that the packages are always
    18  // kept in the "unpacked" form (a directory containing the contents of the
    19  // original distribution archive) so that they are ready for direct execution.
    20  //
    21  // A Dir also pays attention only to packages for the current host platform,
    22  // silently ignoring any cached packages for other platforms.
    23  //
    24  // Various Dir methods return values that are technically mutable due to the
    25  // restrictions of the Go typesystem, but callers are not permitted to mutate
    26  // any part of the returned data structures.
    27  type Dir struct {
    28  	baseDir        string
    29  	targetPlatform getproviders.Platform
    30  
    31  	// metaCache is a cache of the metadata of relevant packages available in
    32  	// the cache directory last time we scanned it. This can be nil to indicate
    33  	// that the cache is cold. The cache will be invalidated (set back to nil)
    34  	// by any operation that modifies the contents of the cache directory.
    35  	//
    36  	// We intentionally don't make effort to detect modifications to the
    37  	// directory made by other codepaths because the contract for NewDir
    38  	// explicitly defines using the same directory for multiple purposes
    39  	// as undefined behavior.
    40  	metaCache map[addrs.Provider][]CachedProvider
    41  }
    42  
    43  // NewDir creates and returns a new Dir object that will read and write
    44  // provider plugins in the given filesystem directory.
    45  //
    46  // If two instances of Dir are concurrently operating on a particular base
    47  // directory, or if a Dir base directory is also used as a filesystem mirror
    48  // source directory, the behavior is undefined.
    49  func NewDir(baseDir string) *Dir {
    50  	return &Dir{
    51  		baseDir:        baseDir,
    52  		targetPlatform: getproviders.CurrentPlatform,
    53  	}
    54  }
    55  
    56  // NewDirWithPlatform is a variant of NewDir that allows selecting a specific
    57  // target platform, rather than taking the current one where this code is
    58  // running.
    59  //
    60  // This is primarily intended for portable unit testing and not particularly
    61  // useful in "real" callers.
    62  func NewDirWithPlatform(baseDir string, platform getproviders.Platform) *Dir {
    63  	return &Dir{
    64  		baseDir:        baseDir,
    65  		targetPlatform: platform,
    66  	}
    67  }
    68  
    69  // BasePath returns the filesystem path of the base directory of this
    70  // cache directory.
    71  func (d *Dir) BasePath() string {
    72  	return filepath.Clean(d.baseDir)
    73  }
    74  
    75  // AllAvailablePackages returns a description of all of the packages already
    76  // present in the directory. The cache entries are grouped by the provider
    77  // they relate to and then sorted by version precedence, with highest
    78  // precedence first.
    79  //
    80  // This function will return an empty result both when the directory is empty
    81  // and when scanning the directory produces an error.
    82  //
    83  // The caller is forbidden from modifying the returned data structure in any
    84  // way, even though the Go type system permits it.
    85  func (d *Dir) AllAvailablePackages() map[addrs.Provider][]CachedProvider {
    86  	if err := d.fillMetaCache(); err != nil {
    87  		log.Printf("[WARN] Failed to scan provider cache directory %s: %s", d.baseDir, err)
    88  		return nil
    89  	}
    90  
    91  	return d.metaCache
    92  }
    93  
    94  // ProviderVersion returns the cache entry for the requested provider version,
    95  // or nil if the requested provider version isn't present in the cache.
    96  func (d *Dir) ProviderVersion(provider addrs.Provider, version getproviders.Version) *CachedProvider {
    97  	if err := d.fillMetaCache(); err != nil {
    98  		return nil
    99  	}
   100  
   101  	for _, entry := range d.metaCache[provider] {
   102  		// We're intentionally comparing exact version here, so if either
   103  		// version number contains build metadata and they don't match then
   104  		// this will not return true. The rule of ignoring build metadata
   105  		// applies only for handling version _constraints_ and for deciding
   106  		// version precedence.
   107  		if entry.Version == version {
   108  			return &entry
   109  		}
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  // ProviderLatestVersion returns the cache entry for the latest
   116  // version of the requested provider already available in the cache, or nil if
   117  // there are no versions of that provider available.
   118  func (d *Dir) ProviderLatestVersion(provider addrs.Provider) *CachedProvider {
   119  	if err := d.fillMetaCache(); err != nil {
   120  		return nil
   121  	}
   122  
   123  	entries := d.metaCache[provider]
   124  	if len(entries) == 0 {
   125  		return nil
   126  	}
   127  
   128  	return &entries[0]
   129  }
   130  
   131  func (d *Dir) fillMetaCache() error {
   132  	// For d.metaCache we consider nil to be different than a non-nil empty
   133  	// map, so we can distinguish between having scanned and got an empty
   134  	// result vs. not having scanned successfully at all yet.
   135  	if d.metaCache != nil {
   136  		log.Printf("[TRACE] providercache.fillMetaCache: using cached result from previous scan of %s", d.baseDir)
   137  		return nil
   138  	}
   139  	log.Printf("[TRACE] providercache.fillMetaCache: scanning directory %s", d.baseDir)
   140  
   141  	allData, err := getproviders.SearchLocalDirectory(d.baseDir)
   142  	if err != nil {
   143  		log.Printf("[TRACE] providercache.fillMetaCache: error while scanning directory %s: %s", d.baseDir, err)
   144  		return err
   145  	}
   146  
   147  	// The getproviders package just returns everything it found, but we're
   148  	// interested only in a subset of the results:
   149  	// - those that are for the current platform
   150  	// - those that are in the "unpacked" form, ready to execute
   151  	// ...so we'll filter in these ways while we're constructing our final
   152  	// map to save as the cache.
   153  	//
   154  	// We intentionally always make a non-nil map, even if it might ultimately
   155  	// be empty, because we use that to recognize that the cache is populated.
   156  	data := make(map[addrs.Provider][]CachedProvider)
   157  
   158  	for providerAddr, metas := range allData {
   159  		for _, meta := range metas {
   160  			if meta.TargetPlatform != d.targetPlatform {
   161  				log.Printf("[TRACE] providercache.fillMetaCache: ignoring %s because it is for %s, not %s", meta.Location, meta.TargetPlatform, d.targetPlatform)
   162  				continue
   163  			}
   164  			if _, ok := meta.Location.(getproviders.PackageLocalDir); !ok {
   165  				// PackageLocalDir indicates an unpacked provider package ready
   166  				// to execute.
   167  				log.Printf("[TRACE] providercache.fillMetaCache: ignoring %s because it is not an unpacked directory", meta.Location)
   168  				continue
   169  			}
   170  
   171  			packageDir := filepath.Clean(string(meta.Location.(getproviders.PackageLocalDir)))
   172  
   173  			log.Printf("[TRACE] providercache.fillMetaCache: including %s as a candidate package for %s %s", meta.Location, providerAddr, meta.Version)
   174  			data[providerAddr] = append(data[providerAddr], CachedProvider{
   175  				Provider:   providerAddr,
   176  				Version:    meta.Version,
   177  				PackageDir: filepath.ToSlash(packageDir),
   178  			})
   179  		}
   180  	}
   181  
   182  	// After we've built our lists per provider, we'll also sort them by
   183  	// version precedence so that the newest available version is always at
   184  	// index zero. If there are two versions that differ only in build metadata
   185  	// then it's undefined but deterministic which one we will select here,
   186  	// because we're preserving the order returned by SearchLocalDirectory
   187  	// in that case..
   188  	for _, entries := range data {
   189  		sort.SliceStable(entries, func(i, j int) bool {
   190  			// We're using GreaterThan rather than LessThan here because we
   191  			// want these in _decreasing_ order of precedence.
   192  			return entries[i].Version.GreaterThan(entries[j].Version)
   193  		})
   194  	}
   195  
   196  	d.metaCache = data
   197  	return nil
   198  }