213 lines
7.4 KiB
Go
213 lines
7.4 KiB
Go
package getproviders
|
|
|
|
import (
|
|
"archive/zip"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
)
|
|
|
|
// MockSource is an in-memory-only, statically-configured source intended for
|
|
// use only in unit tests of other subsystems that consume provider sources.
|
|
//
|
|
// The MockSource also tracks calls to it in case a calling test wishes to
|
|
// assert that particular calls were made.
|
|
//
|
|
// This should not be used outside of unit test code.
|
|
type MockSource struct {
|
|
packages []PackageMeta
|
|
warnings map[addrs.Provider]Warnings
|
|
calls [][]interface{}
|
|
}
|
|
|
|
var _ Source = (*MockSource)(nil)
|
|
|
|
// NewMockSource creates and returns a MockSource with the given packages.
|
|
//
|
|
// The given packages don't necessarily need to refer to objects that actually
|
|
// exist on disk or over the network, unless the calling test is planning to
|
|
// use (directly or indirectly) the results for further provider installation
|
|
// actions.
|
|
func NewMockSource(packages []PackageMeta, warns map[addrs.Provider]Warnings) *MockSource {
|
|
return &MockSource{
|
|
packages: packages,
|
|
warnings: warns,
|
|
}
|
|
}
|
|
|
|
// AvailableVersions returns all of the versions of the given provider that
|
|
// are available in the fixed set of packages that were passed to
|
|
// NewMockSource when creating the receiving source.
|
|
func (s *MockSource) AvailableVersions(provider addrs.Provider) (VersionList, Warnings, error) {
|
|
s.calls = append(s.calls, []interface{}{"AvailableVersions", provider})
|
|
var ret VersionList
|
|
for _, pkg := range s.packages {
|
|
if pkg.Provider == provider {
|
|
ret = append(ret, pkg.Version)
|
|
}
|
|
}
|
|
var warns []string
|
|
if s.warnings != nil {
|
|
if warnings, ok := s.warnings[provider]; ok {
|
|
warns = warnings
|
|
}
|
|
}
|
|
if len(ret) == 0 {
|
|
// In this case, we'll behave like a registry that doesn't know about
|
|
// this provider at all, rather than just returning an empty result.
|
|
return nil, warns, ErrRegistryProviderNotKnown{provider}
|
|
}
|
|
ret.Sort()
|
|
return ret, warns, nil
|
|
}
|
|
|
|
// PackageMeta returns the first package from the list given to NewMockSource
|
|
// when creating the receiver that has the given provider, version, and
|
|
// target platform.
|
|
//
|
|
// If none of the packages match, it returns ErrPlatformNotSupported to
|
|
// simulate the situation where a provider release isn't available for a
|
|
// particular platform.
|
|
//
|
|
// Note that if the list of packages passed to NewMockSource contains more
|
|
// than one with the same provider, version, and target this function will
|
|
// always return the first one in the list, which may not match the behavior
|
|
// of other sources in an equivalent situation because it's a degenerate case
|
|
// with undefined results.
|
|
func (s *MockSource) PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
|
|
s.calls = append(s.calls, []interface{}{"PackageMeta", provider, version, target})
|
|
|
|
for _, pkg := range s.packages {
|
|
if pkg.Provider != provider {
|
|
continue
|
|
}
|
|
if pkg.Version != version {
|
|
// (We're using strict equality rather than precedence here,
|
|
// because this is an exact version specification. The caller
|
|
// should consider precedence when selecting a version in the
|
|
// AvailableVersions response, and pass the exact selected
|
|
// version here.)
|
|
continue
|
|
}
|
|
if pkg.TargetPlatform != target {
|
|
continue
|
|
}
|
|
return pkg, nil
|
|
}
|
|
|
|
// If we fall out here then nothing matched at all, so we'll treat that
|
|
// as "platform not supported" for consistency with RegistrySource.
|
|
return PackageMeta{}, ErrPlatformNotSupported{
|
|
Provider: provider,
|
|
Version: version,
|
|
Platform: target,
|
|
}
|
|
}
|
|
|
|
// CallLog returns a list of calls to other methods of the receiever that have
|
|
// been called since it was created, in case a calling test wishes to verify
|
|
// a particular sequence of operations.
|
|
//
|
|
// The result is a slice of slices where the first element of each inner slice
|
|
// is the name of the method that was called, and then any subsequent elements
|
|
// are positional arguments passed to that method.
|
|
//
|
|
// Callers are forbidden from modifying any objects accessible via the returned
|
|
// value.
|
|
func (s *MockSource) CallLog() [][]interface{} {
|
|
return s.calls
|
|
}
|
|
|
|
// FakePackageMeta constructs and returns a PackageMeta that carries the given
|
|
// metadata but has fake location information that is likely to fail if
|
|
// attempting to install from it.
|
|
func FakePackageMeta(provider addrs.Provider, version Version, protocols VersionList, target Platform) PackageMeta {
|
|
return PackageMeta{
|
|
Provider: provider,
|
|
Version: version,
|
|
ProtocolVersions: protocols,
|
|
TargetPlatform: target,
|
|
|
|
// Some fake but somewhat-realistic-looking other metadata. This
|
|
// points nowhere, so will fail if attempting to actually use it.
|
|
Filename: fmt.Sprintf("terraform-provider-%s_%s_%s.zip", provider.Type, version.String(), target.String()),
|
|
Location: PackageHTTPURL(fmt.Sprintf("https://fake.invalid/terraform-provider-%s_%s.zip", provider.Type, version.String())),
|
|
}
|
|
}
|
|
|
|
// FakeInstallablePackageMeta constructs and returns a PackageMeta that points
|
|
// to a temporary archive file that could actually be installed in principle.
|
|
//
|
|
// Installing it will not produce a working provider though: just a fake file
|
|
// posing as an executable.
|
|
//
|
|
// It's the caller's responsibility to call the close callback returned
|
|
// alongside the result in order to clean up the temporary file. The caller
|
|
// should call the callback even if this function returns an error, because
|
|
// some error conditions leave a partially-created file on disk.
|
|
func FakeInstallablePackageMeta(provider addrs.Provider, version Version, protocols VersionList, target Platform) (PackageMeta, func(), error) {
|
|
f, err := ioutil.TempFile("", "terraform-getproviders-fake-package-")
|
|
if err != nil {
|
|
return PackageMeta{}, func() {}, err
|
|
}
|
|
|
|
// After this point, all of our return paths should include this as the
|
|
// close callback.
|
|
close := func() {
|
|
f.Close()
|
|
os.Remove(f.Name())
|
|
}
|
|
|
|
execFilename := fmt.Sprintf("terraform-provider-%s_%s", provider.Type, version.String())
|
|
if target.OS == "windows" {
|
|
// For a little more (technically unnecessary) realism...
|
|
execFilename += ".exe"
|
|
}
|
|
|
|
zw := zip.NewWriter(f)
|
|
fw, err := zw.Create(execFilename)
|
|
if err != nil {
|
|
return PackageMeta{}, close, fmt.Errorf("failed to add %s to mock zip file: %s", execFilename, err)
|
|
}
|
|
fmt.Fprintf(fw, "This is a fake provider package for %s %s, not a real provider.\n", provider, version)
|
|
err = zw.Close()
|
|
if err != nil {
|
|
return PackageMeta{}, close, fmt.Errorf("failed to close the mock zip file: %s", err)
|
|
}
|
|
|
|
// Compute the SHA256 checksum of the generated file, to allow package
|
|
// authentication code to be exercised.
|
|
f.Seek(0, io.SeekStart)
|
|
h := sha256.New()
|
|
io.Copy(h, f)
|
|
checksum := [32]byte{}
|
|
h.Sum(checksum[:0])
|
|
|
|
meta := PackageMeta{
|
|
Provider: provider,
|
|
Version: version,
|
|
ProtocolVersions: protocols,
|
|
TargetPlatform: target,
|
|
|
|
Location: PackageLocalArchive(f.Name()),
|
|
|
|
// This is a fake filename that mimics what a real registry might
|
|
// indicate as a good filename for this package, in case some caller
|
|
// intends to use it to name a local copy of the temporary file.
|
|
// (At the time of writing, no caller actually does that, but who
|
|
// knows what the future holds?)
|
|
Filename: fmt.Sprintf("terraform-provider-%s_%s_%s.zip", provider.Type, version.String(), target.String()),
|
|
|
|
Authentication: NewArchiveChecksumAuthentication(checksum),
|
|
}
|
|
return meta, close, nil
|
|
}
|
|
|
|
func (s *MockSource) ForDisplay(provider addrs.Provider) string {
|
|
return "mock source"
|
|
}
|