2024-02-08 03:48:59 -06:00
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
2023-05-02 10:33:06 -05:00
// SPDX-License-Identifier: MPL-2.0
2020-01-09 19:47:54 -06:00
package getproviders
import (
2020-09-28 19:13:32 -05:00
"context"
2024-12-11 11:10:49 -06:00
"fmt"
2020-09-28 19:13:32 -05:00
2024-12-11 11:10:49 -06:00
"github.com/apparentlymart/go-versions/versions"
2023-09-20 06:35:35 -05:00
"github.com/opentofu/opentofu/internal/addrs"
2020-01-09 19:47:54 -06:00
)
// FilesystemMirrorSource is a source that reads providers and their metadata
// from a directory prefix in the local filesystem.
type FilesystemMirrorSource struct {
baseDir string
2020-02-20 20:01:29 -06:00
// 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
2020-01-09 19:47:54 -06:00
}
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.
2020-09-28 19:13:32 -05:00
func ( s * FilesystemMirrorSource ) AvailableVersions ( ctx context . Context , provider addrs . Provider ) ( VersionList , Warnings , error ) {
2020-02-20 20:01:29 -06:00
// s.allPackages is populated if scanAllVersions succeeds
err := s . scanAllVersions ( )
if err != nil {
2020-06-25 09:49:48 -05:00
return nil , nil , err
2020-02-20 20:01:29 -06:00
}
// 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 ( )
2024-12-11 11:10:49 -06:00
// 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
2020-01-09 19:47:54 -06:00
}
// 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.
2020-09-28 19:13:32 -05:00
func ( s * FilesystemMirrorSource ) PackageMeta ( ctx context . Context , provider addrs . Provider , version Version , target Platform ) ( PackageMeta , error ) {
2020-02-20 20:01:29 -06:00
// 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
}
2020-03-11 11:38:55 -05:00
ret , err := SearchLocalDirectory ( s . baseDir )
2020-02-20 20:01:29 -06:00
if err != nil {
return err
}
2020-03-11 11:38:55 -05:00
// 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 )
2020-02-20 20:01:29 -06:00
}
s . allPackages = ret
return nil
2020-01-09 19:47:54 -06:00
}
2020-05-14 13:04:13 -05:00
func ( s * FilesystemMirrorSource ) ForDisplay ( provider addrs . Provider ) string {
return s . baseDir
}