github.com/opentofu/opentofu@v1.7.1/internal/getproviders/mock_source.go (about)

     1  package getproviders
     2  
     3  import (
     4  	"archive/zip"
     5  	"context"
     6  	"crypto/sha256"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  
    11  	"github.com/opentofu/opentofu/internal/addrs"
    12  )
    13  
    14  // MockSource is an in-memory-only, statically-configured source intended for
    15  // use only in unit tests of other subsystems that consume provider sources.
    16  //
    17  // The MockSource also tracks calls to it in case a calling test wishes to
    18  // assert that particular calls were made.
    19  //
    20  // This should not be used outside of unit test code.
    21  type MockSource struct {
    22  	packages []PackageMeta
    23  	warnings map[addrs.Provider]Warnings
    24  	calls    [][]interface{}
    25  }
    26  
    27  var _ Source = (*MockSource)(nil)
    28  
    29  // NewMockSource creates and returns a MockSource with the given packages.
    30  //
    31  // The given packages don't necessarily need to refer to objects that actually
    32  // exist on disk or over the network, unless the calling test is planning to
    33  // use (directly or indirectly) the results for further provider installation
    34  // actions.
    35  func NewMockSource(packages []PackageMeta, warns map[addrs.Provider]Warnings) *MockSource {
    36  	return &MockSource{
    37  		packages: packages,
    38  		warnings: warns,
    39  	}
    40  }
    41  
    42  // AvailableVersions returns all of the versions of the given provider that
    43  // are available in the fixed set of packages that were passed to
    44  // NewMockSource when creating the receiving source.
    45  func (s *MockSource) AvailableVersions(ctx context.Context, provider addrs.Provider) (VersionList, Warnings, error) {
    46  	s.calls = append(s.calls, []interface{}{"AvailableVersions", provider})
    47  	var ret VersionList
    48  	for _, pkg := range s.packages {
    49  		if pkg.Provider == provider {
    50  			ret = append(ret, pkg.Version)
    51  		}
    52  	}
    53  	var warns []string
    54  	if s.warnings != nil {
    55  		if warnings, ok := s.warnings[provider]; ok {
    56  			warns = warnings
    57  		}
    58  	}
    59  	if len(ret) == 0 {
    60  		// In this case, we'll behave like a registry that doesn't know about
    61  		// this provider at all, rather than just returning an empty result.
    62  		return nil, warns, ErrRegistryProviderNotKnown{provider}
    63  	}
    64  	ret.Sort()
    65  	return ret, warns, nil
    66  }
    67  
    68  // PackageMeta returns the first package from the list given to NewMockSource
    69  // when creating the receiver that has the given provider, version, and
    70  // target platform.
    71  //
    72  // If none of the packages match, it returns ErrPlatformNotSupported to
    73  // simulate the situation where a provider release isn't available for a
    74  // particular platform.
    75  //
    76  // Note that if the list of packages passed to NewMockSource contains more
    77  // than one with the same provider, version, and target this function will
    78  // always return the first one in the list, which may not match the behavior
    79  // of other sources in an equivalent situation because it's a degenerate case
    80  // with undefined results.
    81  func (s *MockSource) PackageMeta(ctx context.Context, provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
    82  	s.calls = append(s.calls, []interface{}{"PackageMeta", provider, version, target})
    83  
    84  	for _, pkg := range s.packages {
    85  		if pkg.Provider != provider {
    86  			continue
    87  		}
    88  		if pkg.Version != version {
    89  			// (We're using strict equality rather than precedence here,
    90  			// because this is an exact version specification. The caller
    91  			// should consider precedence when selecting a version in the
    92  			// AvailableVersions response, and pass the exact selected
    93  			// version here.)
    94  			continue
    95  		}
    96  		if pkg.TargetPlatform != target {
    97  			continue
    98  		}
    99  		return pkg, nil
   100  	}
   101  
   102  	// If we fall out here then nothing matched at all, so we'll treat that
   103  	// as "platform not supported" for consistency with RegistrySource.
   104  	return PackageMeta{}, ErrPlatformNotSupported{
   105  		Provider: provider,
   106  		Version:  version,
   107  		Platform: target,
   108  	}
   109  }
   110  
   111  // CallLog returns a list of calls to other methods of the receiever that have
   112  // been called since it was created, in case a calling test wishes to verify
   113  // a particular sequence of operations.
   114  //
   115  // The result is a slice of slices where the first element of each inner slice
   116  // is the name of the method that was called, and then any subsequent elements
   117  // are positional arguments passed to that method.
   118  //
   119  // Callers are forbidden from modifying any objects accessible via the returned
   120  // value.
   121  func (s *MockSource) CallLog() [][]interface{} {
   122  	return s.calls
   123  }
   124  
   125  // FakePackageMeta constructs and returns a PackageMeta that carries the given
   126  // metadata but has fake location information that is likely to fail if
   127  // attempting to install from it.
   128  func FakePackageMeta(provider addrs.Provider, version Version, protocols VersionList, target Platform) PackageMeta {
   129  	return PackageMeta{
   130  		Provider:         provider,
   131  		Version:          version,
   132  		ProtocolVersions: protocols,
   133  		TargetPlatform:   target,
   134  
   135  		// Some fake but somewhat-realistic-looking other metadata. This
   136  		// points nowhere, so will fail if attempting to actually use it.
   137  		Filename: fmt.Sprintf("terraform-provider-%s_%s_%s.zip", provider.Type, version.String(), target.String()),
   138  		Location: PackageHTTPURL(fmt.Sprintf("https://fake.invalid/terraform-provider-%s_%s.zip", provider.Type, version.String())),
   139  	}
   140  }
   141  
   142  // FakeInstallablePackageMeta constructs and returns a PackageMeta that points
   143  // to a temporary archive file that could actually be installed in principle.
   144  //
   145  // Installing it will not produce a working provider though: just a fake file
   146  // posing as an executable. The filename for the executable defaults to the
   147  // standard terraform-provider-NAME_X.Y.Z format, but can be overridden with
   148  // the execFilename argument.
   149  //
   150  // It's the caller's responsibility to call the close callback returned
   151  // alongside the result in order to clean up the temporary file. The caller
   152  // should call the callback even if this function returns an error, because
   153  // some error conditions leave a partially-created file on disk.
   154  func FakeInstallablePackageMeta(provider addrs.Provider, version Version, protocols VersionList, target Platform, execFilename string) (PackageMeta, func(), error) {
   155  	f, err := os.CreateTemp("", "tofu-getproviders-fake-package-")
   156  	if err != nil {
   157  		return PackageMeta{}, func() {}, err
   158  	}
   159  
   160  	// After this point, all of our return paths should include this as the
   161  	// close callback.
   162  	close := func() {
   163  		f.Close()
   164  		os.Remove(f.Name())
   165  	}
   166  
   167  	if execFilename == "" {
   168  		execFilename = fmt.Sprintf("terraform-provider-%s_%s", provider.Type, version.String())
   169  		if target.OS == "windows" {
   170  			// For a little more (technically unnecessary) realism...
   171  			execFilename += ".exe"
   172  		}
   173  	}
   174  
   175  	zw := zip.NewWriter(f)
   176  	fw, err := zw.Create(execFilename)
   177  	if err != nil {
   178  		return PackageMeta{}, close, fmt.Errorf("failed to add %s to mock zip file: %w", execFilename, err)
   179  	}
   180  	fmt.Fprintf(fw, "This is a fake provider package for %s %s, not a real provider.\n", provider, version)
   181  	err = zw.Close()
   182  	if err != nil {
   183  		return PackageMeta{}, close, fmt.Errorf("failed to close the mock zip file: %w", err)
   184  	}
   185  
   186  	// Compute the SHA256 checksum of the generated file, to allow package
   187  	// authentication code to be exercised.
   188  	f.Seek(0, io.SeekStart)
   189  	h := sha256.New()
   190  	io.Copy(h, f)
   191  	checksum := [32]byte{}
   192  	h.Sum(checksum[:0])
   193  
   194  	meta := PackageMeta{
   195  		Provider:         provider,
   196  		Version:          version,
   197  		ProtocolVersions: protocols,
   198  		TargetPlatform:   target,
   199  
   200  		Location: PackageLocalArchive(f.Name()),
   201  
   202  		// This is a fake filename that mimics what a real registry might
   203  		// indicate as a good filename for this package, in case some caller
   204  		// intends to use it to name a local copy of the temporary file.
   205  		// (At the time of writing, no caller actually does that, but who
   206  		// knows what the future holds?)
   207  		Filename: fmt.Sprintf("terraform-provider-%s_%s_%s.zip", provider.Type, version.String(), target.String()),
   208  
   209  		Authentication: NewArchiveChecksumAuthentication(target, checksum),
   210  	}
   211  	return meta, close, nil
   212  }
   213  
   214  func (s *MockSource) ForDisplay(provider addrs.Provider) string {
   215  	return "mock source"
   216  }