Skip to content

Commit 44808d4

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

File tree

1 file changed

+65
-4
lines changed

1 file changed

+65
-4
lines changed

pkg/utils/terraform_utils.go

Lines changed: 65 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,56 @@ 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+
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
3284
func 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

Comments
 (0)