33package terraform_test
44
55import (
6- "bytes"
76 "context"
8- "crypto/sha256"
9- "encoding/hex"
107 "encoding/json"
118 "errors"
129 "fmt"
1310 "net"
1411 "net/http"
1512 "os"
16- "os/exec"
1713 "path/filepath"
1814 "sort"
1915 "strings"
@@ -94,168 +90,6 @@ func configure(ctx context.Context, t *testing.T, client proto.DRPCProvisionerCl
9490 return sess
9591}
9692
97- func hashTemplateFilesAndTestName (t * testing.T , testName string , templateFiles map [string ]string ) string {
98- t .Helper ()
99-
100- sortedFileNames := make ([]string , 0 , len (templateFiles ))
101- for fileName := range templateFiles {
102- sortedFileNames = append (sortedFileNames , fileName )
103- }
104- sort .Strings (sortedFileNames )
105-
106- // Inserting a delimiter between the file name and the file content
107- // ensures that a file named `ab` with content `cd`
108- // will not hash to the same value as a file named `abc` with content `d`.
109- // This can still happen if the file name or content include the delimiter,
110- // but hopefully they won't.
111- delimiter := []byte ("🎉 🌱 🌷" )
112-
113- hasher := sha256 .New ()
114- for _ , fileName := range sortedFileNames {
115- file := templateFiles [fileName ]
116- _ , err := hasher .Write ([]byte (fileName ))
117- require .NoError (t , err )
118- _ , err = hasher .Write (delimiter )
119- require .NoError (t , err )
120- _ , err = hasher .Write ([]byte (file ))
121- require .NoError (t , err )
122- }
123- _ , err := hasher .Write (delimiter )
124- require .NoError (t , err )
125- _ , err = hasher .Write ([]byte (testName ))
126- require .NoError (t , err )
127-
128- return hex .EncodeToString (hasher .Sum (nil ))
129- }
130-
131- const (
132- terraformConfigFileName = "terraform.rc"
133- cacheProvidersDirName = "providers"
134- cacheTemplateFilesDirName = "files"
135- )
136-
137- // Writes a Terraform CLI config file (`terraform.rc`) in `dir` to enforce using the local provider mirror.
138- // This blocks network access for providers, forcing Terraform to use only what's cached in `dir`.
139- // Returns the path to the generated config file.
140- func writeCliConfig (t * testing.T , dir string ) string {
141- t .Helper ()
142-
143- cliConfigPath := filepath .Join (dir , terraformConfigFileName )
144- require .NoError (t , os .MkdirAll (filepath .Dir (cliConfigPath ), 0o700 ))
145-
146- content := fmt .Sprintf (`
147- provider_installation {
148- filesystem_mirror {
149- path = "%s"
150- include = ["*/*"]
151- }
152- direct {
153- exclude = ["*/*"]
154- }
155- }
156- ` , filepath .Join (dir , cacheProvidersDirName ))
157- require .NoError (t , os .WriteFile (cliConfigPath , []byte (content ), 0o600 ))
158- return cliConfigPath
159- }
160-
161- func runCmd (t * testing.T , dir string , args ... string ) {
162- t .Helper ()
163-
164- stdout , stderr := bytes .NewBuffer (nil ), bytes .NewBuffer (nil )
165- cmd := exec .Command (args [0 ], args [1 :]... ) //#nosec
166- cmd .Dir = dir
167- cmd .Stdout = stdout
168- cmd .Stderr = stderr
169- if err := cmd .Run (); err != nil {
170- t .Fatalf ("failed to run %s: %s\n stdout: %s\n stderr: %s" , strings .Join (args , " " ), err , stdout .String (), stderr .String ())
171- }
172- }
173-
174- // Each test gets a unique cache dir based on its name and template files.
175- // This ensures that tests can download providers in parallel and that they
176- // will redownload providers if the template files change.
177- func getTestCacheDir (t * testing.T , rootDir string , testName string , templateFiles map [string ]string ) string {
178- t .Helper ()
179-
180- hash := hashTemplateFilesAndTestName (t , testName , templateFiles )
181- dir := filepath .Join (rootDir , hash [:12 ])
182- return dir
183- }
184-
185- // Ensures Terraform providers are downloaded and cached locally in a unique directory for the test.
186- // Uses `terraform init` then `mirror` to populate the cache if needed.
187- // Returns the cache directory path.
188- func downloadProviders (t * testing.T , rootDir string , testName string , templateFiles map [string ]string ) string {
189- t .Helper ()
190-
191- dir := getTestCacheDir (t , rootDir , testName , templateFiles )
192- if _ , err := os .Stat (dir ); err == nil {
193- t .Logf ("%s: using cached terraform providers" , testName )
194- return dir
195- }
196- filesDir := filepath .Join (dir , cacheTemplateFilesDirName )
197- defer func () {
198- // The files dir will contain a copy of terraform providers generated
199- // by the terraform init command. We don't want to persist them since
200- // we already have a registry mirror in the providers dir.
201- if err := os .RemoveAll (filesDir ); err != nil {
202- t .Logf ("failed to remove files dir %s: %s" , filesDir , err )
203- }
204- if ! t .Failed () {
205- return
206- }
207- // If `downloadProviders` function failed, clean up the cache dir.
208- // We don't want to leave it around because it may be incomplete or corrupted.
209- if err := os .RemoveAll (dir ); err != nil {
210- t .Logf ("failed to remove dir %s: %s" , dir , err )
211- }
212- }()
213-
214- require .NoError (t , os .MkdirAll (filesDir , 0o700 ))
215-
216- for fileName , file := range templateFiles {
217- filePath := filepath .Join (filesDir , fileName )
218- require .NoError (t , os .MkdirAll (filepath .Dir (filePath ), 0o700 ))
219- require .NoError (t , os .WriteFile (filePath , []byte (file ), 0o600 ))
220- }
221-
222- providersDir := filepath .Join (dir , cacheProvidersDirName )
223- require .NoError (t , os .MkdirAll (providersDir , 0o700 ))
224-
225- // We need to run init because if a test uses modules in its template,
226- // the mirror command will fail without it.
227- runCmd (t , filesDir , "terraform" , "init" )
228- // Now, mirror the providers into `providersDir`. We use this explicit mirror
229- // instead of relying only on the standard Terraform plugin cache.
230- //
231- // Why? Because this mirror, when used with the CLI config from `writeCliConfig`,
232- // prevents Terraform from hitting the network registry during `plan`. This cuts
233- // down on network calls, making CI tests less flaky.
234- //
235- // In contrast, the standard cache *still* contacts the registry for metadata
236- // during `init`, even if the plugins are already cached locally - see link below.
237- //
238- // Ref: https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache
239- // > When a plugin cache directory is enabled, the terraform init command will
240- // > still use the configured or implied installation methods to obtain metadata
241- // > about which plugins are available
242- runCmd (t , filesDir , "terraform" , "providers" , "mirror" , providersDir )
243-
244- return dir
245- }
246-
247- // Caches providers locally and generates a Terraform CLI config to use *only* that cache.
248- // This setup prevents network access for providers during `terraform init`, improving reliability
249- // in subsequent test runs.
250- // Returns the path to the generated CLI config file.
251- func cacheProviders (t * testing.T , rootDir string , testName string , templateFiles map [string ]string ) string {
252- t .Helper ()
253-
254- providersParentDir := downloadProviders (t , rootDir , testName , templateFiles )
255- cliConfigPath := writeCliConfig (t , providersParentDir )
256- return cliConfigPath
257- }
258-
25993func readProvisionLog (t * testing.T , response proto.DRPCProvisioner_SessionClient ) string {
26094 var logBuf strings.Builder
26195 for {
@@ -1177,7 +1011,7 @@ func TestProvision(t *testing.T) {
11771011 cacheRootDir := filepath .Join (testutil .PersistentCacheDir (t ), "terraform_provision_test" )
11781012 expectedCacheDirs := make (map [string ]bool )
11791013 for _ , testCase := range testCases {
1180- cacheDir := getTestCacheDir (t , cacheRootDir , testCase .Name , testCase .Files )
1014+ cacheDir := testutil . GetTestTFCacheDir (t , cacheRootDir , testCase .Name , testCase .Files )
11811015 expectedCacheDirs [cacheDir ] = true
11821016 }
11831017 currentCacheDirs , err := filepath .Glob (filepath .Join (cacheRootDir , "*" ))
@@ -1199,7 +1033,7 @@ func TestProvision(t *testing.T) {
11991033
12001034 cliConfigPath := ""
12011035 if ! testCase .SkipCacheProviders {
1202- cliConfigPath = cacheProviders (
1036+ cliConfigPath = testutil . CacheTFProviders (
12031037 t ,
12041038 cacheRootDir ,
12051039 testCase .Name ,
0 commit comments