Skip to content

Commit a6de87d

Browse files
committed
fix: add a lock file logic as the terraform is spawned as a subprocess
1 parent a4c8140 commit a6de87d

File tree

1 file changed

+58
-4
lines changed

1 file changed

+58
-4
lines changed

pkg/utils/terraform_utils.go

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import (
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,51 @@ var templates embed.FS
2830

2931
var 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+
syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN)
75+
return lockFile.Close()
76+
}
77+
3178
// CreateTerraformFileFromTemplate populates a Terraform template and create files in the state
3279
func CreateTerraformFilesFromTemplate(terraformTemplateFilePath string, TerraformOutputFileName string, terraformOutputDir string, templateData any) error {
3380
template := Template{
@@ -99,24 +146,31 @@ func initTerraform(ctx context.Context, workingDir, terraformExecPath string, cr
99146
return nil, err
100147
}
101148

149+
pluginCacheDir := fmt.Sprintf("%s/plugin-cache", filepath.Dir(terraformExecPath))
150+
102151
env := map[string]string{
103152
"AWS_ACCESS_KEY_ID": credentials.AccessKeyID,
104153
"AWS_SECRET_ACCESS_KEY": credentials.SecretAccessKey,
105154
"SPOTINST_TOKEN": os.Getenv("SPOTINST_TOKEN"),
106155
"SPOTINST_ACCOUNT": os.Getenv("SPOTINST_ACCOUNT"),
107-
"TF_PLUGIN_CACHE_DIR": fmt.Sprintf("%s/plugin-cache", filepath.Dir(terraformExecPath)),
156+
"TF_PLUGIN_CACHE_DIR": pluginCacheDir,
108157
}
109158

110-
// this overrides all ENVVARs that are passed to Terraform
111159
err = tf.SetEnv(env)
112160
if err != nil {
113161
return nil, err
114162
}
115163

116164
tfPluginMux.Lock()
117-
err = tf.Init(ctx, tfexec.Upgrade(true))
118-
tfPluginMux.Unlock()
165+
defer tfPluginMux.Unlock()
166+
167+
lockFile, err := lockPluginCache(pluginCacheDir)
168+
if err != nil {
169+
return nil, fmt.Errorf("failed to acquire plugin cache lock: %w", err)
170+
}
171+
defer unlockPluginCache(lockFile)
119172

173+
err = tf.Init(ctx, tfexec.Upgrade(true))
120174
if err != nil {
121175
return nil, err
122176
}

0 commit comments

Comments
 (0)