88 "sync"
99
1010 getter "github.com/hashicorp/go-getter/v2"
11+ "golang.org/x/sync/singleflight"
1112
1213 "github.com/gruntwork-io/terratest/modules/logger"
1314 "github.com/gruntwork-io/terratest/modules/testing"
@@ -16,6 +17,10 @@ import (
1617var (
1718 // A map that maps the go-getter base URL to the temporary directory where it is downloaded.
1819 policyDirCache sync.Map
20+
21+ // downloadGroup deduplicates concurrent downloads for the same baseDir so that N parallel callers requesting the
22+ // same rulePath result in a single underlying download rather than N separate downloads racing into N temp dirs.
23+ downloadGroup singleflight.Group
1924)
2025
2126// DownloadPolicyE takes in a rule path written in go-getter syntax and downloads it to a temporary directory so that it
@@ -52,13 +57,38 @@ func DownloadPolicyE(t testing.TestingT, rulePath string) (string, error) {
5257 // First, check if we had already downloaded the source and it is in our cache.
5358 baseDir , subDir := getter .SourceDirSubdir (rulePath )
5459
55- downloadPath , hasDownloaded := policyDirCache .Load (baseDir )
56- if hasDownloaded {
60+ if downloadPath , hasDownloaded := policyDirCache .Load (baseDir ); hasDownloaded {
5761 logger .Default .Logf (t , "Previously downloaded %s: returning cached path" , baseDir )
5862 return filepath .Join (downloadPath .(string ), subDir ), nil
5963 }
6064
61- // Not downloaded, so use go-getter to download the remote source to a temp dir.
65+ // Cache miss. Use singleflight to ensure that only one goroutine actually performs the download for a given
66+ // baseDir; any concurrent callers block on the same call and reuse its result.
67+ v , err , _ := downloadGroup .Do (baseDir , func () (any , error ) {
68+ // Re-check the cache in case another goroutine populated it while we were waiting to enter the singleflight.
69+ if downloadPath , hasDownloaded := policyDirCache .Load (baseDir ); hasDownloaded {
70+ return downloadPath .(string ), nil
71+ }
72+
73+ tempDir , err := downloadPolicyToTempDir (t , rulePath , baseDir )
74+ if err != nil {
75+ return "" , err
76+ }
77+
78+ policyDirCache .Store (baseDir , tempDir )
79+
80+ return tempDir , nil
81+ })
82+ if err != nil {
83+ return "" , err
84+ }
85+
86+ return filepath .Join (v .(string ), subDir ), nil
87+ }
88+
89+ // downloadPolicyToTempDir downloads the given baseDir using go-getter into a fresh temp directory and returns the path
90+ // to the directory containing the downloaded source.
91+ func downloadPolicyToTempDir (t testing.TestingT , rulePath , baseDir string ) (string , error ) {
6292 tempDir , err := os .MkdirTemp ("" , "terratest-opa-policy-*" )
6393 if err != nil {
6494 return "" , fmt .Errorf ("creating temp directory for policy download: %w" , err )
@@ -74,7 +104,5 @@ func DownloadPolicyE(t testing.TestingT, rulePath string) (string, error) {
74104 return "" , fmt .Errorf ("downloading policy from %s: %w" , baseDir , err )
75105 }
76106
77- policyDirCache .Store (baseDir , tempDir )
78-
79- return filepath .Join (tempDir , subDir ), nil
107+ return tempDir , nil
80108}
0 commit comments