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