| // Copyright (c) The OpenTofu Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| // Copyright (c) 2023 HashiCorp, Inc. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| package providercache | |
| import ( | |
| "log" | |
| "path/filepath" | |
| "sort" | |
| "github.com/opentofu/opentofu/internal/addrs" | |
| "github.com/opentofu/opentofu/internal/getproviders" | |
| ) | |
| // NOTE: The functions in this file are only used in testing and are not optimized! | |
| // The getproviders.SearchLocalDirectory call or allAvailablePackages could be | |
| // cached if these functions are needed by non-test features. | |
| // AllAvailablePackages returns a description of all of the packages already | |
| // present in the directory. The cache entries are grouped by the provider | |
| // they relate to and then sorted by version precedence, with highest | |
| // precedence first. | |
| // | |
| // This function will return an empty result both when the directory is empty | |
| // and when scanning the directory produces an error. | |
| // | |
| // The caller is forbidden from modifying the returned data structure in any | |
| // way, even though the Go type system permits it. | |
| // | |
| // Not performant / cached as it's only used in tests! | |
| func (d *Dir) AllAvailablePackages() map[addrs.Provider][]CachedProvider { | |
| metaCache, err := d.allAvailablePackages() | |
| if err != nil { | |
| log.Printf("[WARN] Failed to scan provider cache directory %s: %s", d.baseDir, err) | |
| return nil | |
| } | |
| return metaCache | |
| } | |
| // ProviderLatestVersion returns the cache entry for the latest | |
| // version of the requested provider already available in the cache, or nil if | |
| // there are no versions of that provider available. | |
| // | |
| // Not performant / cached as it's only used in tests! | |
| func (d *Dir) ProviderLatestVersion(provider addrs.Provider) *CachedProvider { | |
| metaCache, err := d.allAvailablePackages() | |
| if err != nil { | |
| return nil | |
| } | |
| entries := metaCache[provider] | |
| if len(entries) == 0 { | |
| return nil | |
| } | |
| return &entries[0] | |
| } | |
| // Not performant / cached as it's only used in tests! | |
| func (d *Dir) allAvailablePackages() (map[addrs.Provider][]CachedProvider, error) { | |
| log.Printf("[TRACE] providercache.fillMetaCache: scanning directory %s", d.baseDir) | |
| allData, err := getproviders.SearchLocalDirectory(d.baseDir) | |
| if err != nil { | |
| log.Printf("[TRACE] providercache.fillMetaCache: error while scanning directory %s: %s", d.baseDir, err) | |
| return nil, err | |
| } | |
| // The getproviders package just returns everything it found, but we're | |
| // interested only in a subset of the results: | |
| // - those that are for the current platform | |
| // - those that are in the "unpacked" form, ready to execute | |
| // ...so we'll filter in these ways while we're constructing our final | |
| // map to save as the cache. | |
| // | |
| // We intentionally always make a non-nil map, even if it might ultimately | |
| // be empty, because we use that to recognize that the cache is populated. | |
| data := make(map[addrs.Provider][]CachedProvider) | |
| for providerAddr, metas := range allData { | |
| for _, meta := range metas { | |
| if meta.TargetPlatform != d.targetPlatform { | |
| log.Printf("[TRACE] providercache.fillMetaCache: ignoring %s because it is for %s, not %s", meta.Location, meta.TargetPlatform, d.targetPlatform) | |
| continue | |
| } | |
| if _, ok := meta.Location.(getproviders.PackageLocalDir); !ok { | |
| // PackageLocalDir indicates an unpacked provider package ready | |
| // to execute. | |
| log.Printf("[TRACE] providercache.fillMetaCache: ignoring %s because it is not an unpacked directory", meta.Location) | |
| continue | |
| } | |
| packageDir := filepath.Clean(string(meta.Location.(getproviders.PackageLocalDir))) | |
| log.Printf("[TRACE] providercache.fillMetaCache: including %s as a candidate package for %s %s", meta.Location, providerAddr, meta.Version) | |
| data[providerAddr] = append(data[providerAddr], CachedProvider{ | |
| Provider: providerAddr, | |
| Version: meta.Version, | |
| PackageDir: filepath.ToSlash(packageDir), | |
| }) | |
| } | |
| } | |
| // After we've built our lists per provider, we'll also sort them by | |
| // version precedence so that the newest available version is always at | |
| // index zero. If there are two versions that differ only in build metadata | |
| // then it's undefined but deterministic which one we will select here, | |
| // because we're preserving the order returned by SearchLocalDirectory | |
| // in that case.. | |
| for _, entries := range data { | |
| sort.SliceStable(entries, func(i, j int) bool { | |
| // We're using GreaterThan rather than LessThan here because we | |
| // want these in _decreasing_ order of precedence. | |
| return entries[i].Version.GreaterThan(entries[j].Version) | |
| }) | |
| } | |
| return data, nil | |
| } |