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 }