opentofu/internal/getproviders/filesystem_mirror_source.go
Ilia Gogotchuri 267b7f6008
Warn user about provider version 0.0.0 (#2281)
Signed-off-by: Ilia Gogotchuri <ilia.gogotchuri0@gmail.com>
2024-12-11 21:10:49 +04:00

145 lines
5.5 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package getproviders
import (
"context"
"fmt"
"github.com/apparentlymart/go-versions/versions"
"github.com/opentofu/opentofu/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()
// Check the existence of provider version 0.0.0 in the filesystem and warn the user about it
// If it exists, it will be the first element in the sorted list
var warnings Warnings
if len(ret) > 0 && ret[0] == versions.Unspecified {
warning := fmt.Sprintf("Provider %s has an unspecified (0.0.0) version available in the filesystem mirror, source at %s. It will not be used. \n"+
"If the version 0.0.0 is intended to represent a non-published provider, consider using dev_overrides - https://opentofu.org/docs/cli/config/config-file/#development-overrides-for-provider-developers",
provider, s.baseDir)
warnings = append(warnings, warning)
}
return ret, warnings, 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
}