| package getproviders |
| |
| import ( |
| "archive/zip" |
| "context" |
| "crypto/sha256" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "os" |
| |
| "github.com/hashicorp/terraform/internal/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(ctx context.Context, 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(ctx context.Context, 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. The filename for the executable defaults to the |
| // standard terraform-provider-NAME_X.Y.Z format, but can be overridden with |
| // the execFilename argument. |
| // |
| // 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, execFilename string) (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()) |
| } |
| |
| if execFilename == "" { |
| 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(target, checksum), |
| } |
| return meta, close, nil |
| } |
| |
| func (s *MockSource) ForDisplay(provider addrs.Provider) string { |
| return "mock source" |
| } |