| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package getproviders |
| |
| import ( |
| "context" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| ) |
| |
| // FilesystemMirrorSource is a source that reads providers and their metadata |
| // from a directory prefix in the local filesystem. |
| type FilesystemMirrorSource struct { |
| baseDir string |
| |
| // allPackages caches the result of scanning the baseDir for all available |
| // packages on the first call that needs package availability information, |
| // to avoid re-scanning the filesystem on subsequent operations. |
| allPackages map[addrs.Provider]PackageMetaList |
| } |
| |
| var _ Source = (*FilesystemMirrorSource)(nil) |
| |
| // NewFilesystemMirrorSource constructs and returns a new filesystem-based |
| // mirror source with the given base directory. |
| func NewFilesystemMirrorSource(baseDir string) *FilesystemMirrorSource { |
| return &FilesystemMirrorSource{ |
| baseDir: baseDir, |
| } |
| } |
| |
| // AvailableVersions scans the directory structure under the source's base |
| // directory for locally-mirrored packages for the given provider, returning |
| // a list of version numbers for the providers it found. |
| func (s *FilesystemMirrorSource) AvailableVersions(ctx context.Context, provider addrs.Provider) (VersionList, Warnings, error) { |
| // s.allPackages is populated if scanAllVersions succeeds |
| err := s.scanAllVersions() |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| // There might be multiple packages for a given version in the filesystem, |
| // but the contract here is to return distinct versions so we'll dedupe |
| // them first, then sort them, and then return them. |
| versionsMap := make(map[Version]struct{}) |
| for _, m := range s.allPackages[provider] { |
| versionsMap[m.Version] = struct{}{} |
| } |
| ret := make(VersionList, 0, len(versionsMap)) |
| for v := range versionsMap { |
| ret = append(ret, v) |
| } |
| ret.Sort() |
| return ret, nil, nil |
| } |
| |
| // PackageMeta checks to see if the source's base directory contains a |
| // local copy of the distribution package for the given provider version on |
| // the given target, and returns the metadata about it if so. |
| func (s *FilesystemMirrorSource) PackageMeta(ctx context.Context, provider addrs.Provider, version Version, target Platform) (PackageMeta, error) { |
| // s.allPackages is populated if scanAllVersions succeeds |
| err := s.scanAllVersions() |
| if err != nil { |
| return PackageMeta{}, err |
| } |
| |
| relevantPkgs := s.allPackages[provider].FilterProviderPlatformExactVersion(provider, target, version) |
| if len(relevantPkgs) == 0 { |
| // This is the local equivalent of a "404 Not Found" when retrieving |
| // a particular version from a registry or network mirror. Because |
| // the caller should've selected a version already found by |
| // AvailableVersions, the only discriminator that should fail here |
| // is the target platform, and so our error result assumes that, |
| // causing the caller to return an error like "This provider version is |
| // not compatible with aros_riscv". |
| return PackageMeta{}, ErrPlatformNotSupported{ |
| Provider: provider, |
| Version: version, |
| Platform: target, |
| } |
| } |
| |
| // It's possible that there could be multiple copies of the same package |
| // available in the filesystem, if e.g. there's both a packed and an |
| // unpacked variant. For now we assume that the decision between them |
| // is arbitrary and just take the first one in the result. |
| return relevantPkgs[0], nil |
| } |
| |
| // AllAvailablePackages scans the directory structure under the source's base |
| // directory for locally-mirrored packages for all providers, returning a map |
| // of the discovered packages with the fully-qualified provider names as |
| // keys. |
| // |
| // This is not an operation generally supported by all Source implementations, |
| // but the filesystem implementation offers it because we also use the |
| // filesystem mirror source directly to scan our auto-install plugin directory |
| // and in other automatic discovery situations. |
| func (s *FilesystemMirrorSource) AllAvailablePackages() (map[addrs.Provider]PackageMetaList, error) { |
| // s.allPackages is populated if scanAllVersions succeeds |
| err := s.scanAllVersions() |
| return s.allPackages, err |
| } |
| |
| func (s *FilesystemMirrorSource) scanAllVersions() error { |
| if s.allPackages != nil { |
| // we're distinguishing nil-ness from emptiness here so we can |
| // recognize when we've scanned the directory without errors, even |
| // if we found nothing during the scan. |
| return nil |
| } |
| |
| ret, err := SearchLocalDirectory(s.baseDir) |
| if err != nil { |
| return err |
| } |
| |
| // As noted above, we use an explicit empty map so we can distinguish a |
| // successful-but-empty result from a failure on future calls, so we'll |
| // make sure that's what we have before we assign it here. |
| if ret == nil { |
| ret = make(map[addrs.Provider]PackageMetaList) |
| } |
| s.allPackages = ret |
| return nil |
| } |
| |
| func (s *FilesystemMirrorSource) ForDisplay(provider addrs.Provider) string { |
| return s.baseDir |
| } |