99 "regexp"
1010 "strings"
1111 "sync"
12+ "syscall"
1213 "text/template"
14+ "time"
1315
1416 "github.com/aws/aws-sdk-go-v2/aws"
1517 "github.com/hashicorp/terraform-exec/tfexec"
@@ -28,6 +30,56 @@ var templates embed.FS
2830
2931var tfPluginMux sync.Mutex
3032
33+ func lockPluginCache (pluginCacheDir string ) (* os.File , error ) {
34+ if err := os .MkdirAll (pluginCacheDir , 0755 ); err != nil {
35+ return nil , err
36+ }
37+
38+ lockPath := filepath .Join (pluginCacheDir , ".lock" )
39+ lockFile , err := os .OpenFile (lockPath , os .O_CREATE | os .O_RDWR , 0644 )
40+ if err != nil {
41+ return nil , err
42+ }
43+
44+ // Wait up to 5 minutes for the lock
45+ timeout := time .After (5 * time .Minute )
46+ ticker := time .NewTicker (5 * time .Second )
47+ defer ticker .Stop ()
48+
49+ for {
50+ err = syscall .Flock (int (lockFile .Fd ()), syscall .LOCK_EX | syscall .LOCK_NB )
51+ if err == nil {
52+ return lockFile , nil
53+ }
54+
55+ if err != syscall .EWOULDBLOCK {
56+ lockFile .Close ()
57+ return nil , err
58+ }
59+
60+ select {
61+ case <- timeout :
62+ lockFile .Close ()
63+ return nil , fmt .Errorf ("timeout waiting for plugin cache lock" )
64+ case <- ticker .C :
65+ // Retry
66+ }
67+ }
68+ }
69+
70+ func unlockPluginCache (lockFile * os.File ) error {
71+ if lockFile == nil {
72+ return nil
73+ }
74+
75+ if err := syscall .Flock (int (lockFile .Fd ()), syscall .LOCK_UN ); err != nil {
76+ lockFile .Close ()
77+ return fmt .Errorf ("failed to unlock plugin cache: %w" , err )
78+ }
79+
80+ return lockFile .Close ()
81+ }
82+
3183// CreateTerraformFileFromTemplate populates a Terraform template and create files in the state
3284func CreateTerraformFilesFromTemplate (terraformTemplateFilePath string , TerraformOutputFileName string , terraformOutputDir string , templateData any ) error {
3385 template := Template {
@@ -99,24 +151,33 @@ func initTerraform(ctx context.Context, workingDir, terraformExecPath string, cr
99151 return nil , err
100152 }
101153
154+ pluginCacheDir := fmt .Sprintf ("%s/plugin-cache" , filepath .Dir (terraformExecPath ))
155+
102156 env := map [string ]string {
103157 "AWS_ACCESS_KEY_ID" : credentials .AccessKeyID ,
104158 "AWS_SECRET_ACCESS_KEY" : credentials .SecretAccessKey ,
105159 "SPOTINST_TOKEN" : os .Getenv ("SPOTINST_TOKEN" ),
106160 "SPOTINST_ACCOUNT" : os .Getenv ("SPOTINST_ACCOUNT" ),
107- "TF_PLUGIN_CACHE_DIR" : fmt . Sprintf ( "%s/plugin-cache" , filepath . Dir ( terraformExecPath )) ,
161+ "TF_PLUGIN_CACHE_DIR" : pluginCacheDir ,
108162 }
109163
110- // this overrides all ENVVARs that are passed to Terraform
111164 err = tf .SetEnv (env )
112165 if err != nil {
113166 return nil , err
114167 }
115168
116169 tfPluginMux .Lock ()
117- err = tf .Init (ctx , tfexec .Upgrade (true ))
118- tfPluginMux .Unlock ()
170+ defer tfPluginMux .Unlock ()
119171
172+ lockFile , err := lockPluginCache (pluginCacheDir )
173+ if err != nil {
174+ return nil , fmt .Errorf ("failed to acquire plugin cache lock: %w" , err )
175+ }
176+ defer func () {
177+ _ = unlockPluginCache (lockFile )
178+ }()
179+
180+ err = tf .Init (ctx , tfexec .Upgrade (true ))
120181 if err != nil {
121182 return nil , err
122183 }
0 commit comments