Skip to content

Commit

Permalink
fix(tft): parallel TF vet
Browse files Browse the repository at this point in the history
  • Loading branch information
apeabody committed Feb 13, 2024
1 parent 88e9b35 commit 53ab9bc
Showing 1 changed file with 39 additions and 28 deletions.
67 changes: 39 additions & 28 deletions infra/blueprint-test/pkg/tft/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ import (

const (
setupKeyOutputName = "sa_key"
tftCacheMutexFilename = "/tmp/bpt-tft-cache.lock"
tftCacheMutexFilename = "bpt-tft-cache.lock"
vetFilename = "plan.tfplan"
)

var (
Expand Down Expand Up @@ -179,9 +180,9 @@ func NewTFBlueprintTest(t testing.TB, opts ...tftOption) *TFBlueprintTest {
t: t,
}
// initiate tft cache file mutex
tft.tftCacheMutex, err = filemutex.New(tftCacheMutexFilename)
tft.tftCacheMutex, err = filemutex.New(filepath.Join(os.TempDir(), tftCacheMutexFilename))
if err != nil {
t.Fatalf("tft lock file <%s> could not created: %v", tftCacheMutexFilename, err)
t.Fatalf("tft lock file <%s> could not created: %v", filepath.Join(os.TempDir(), tftCacheMutexFilename), err)
}
// default TF blueprint methods
tft.init = tft.DefaultInit
Expand Down Expand Up @@ -277,6 +278,9 @@ func (b *TFBlueprintTest) sensitiveOutputs(dir string) map[string]bool {

// getOutputs returns all output values.
func (b *TFBlueprintTest) getOutputs(sensitive map[string]bool) map[string]interface{} {
// allow only parallel reads as Terraform plugin cache isn't concurrent safe
rUnlockFn := b.rLockFn()
defer rUnlockFn()
outputs := terraform.OutputAll(b.t, &terraform.Options{TerraformDir: b.setupDir, Logger: b.sensitiveLogger, NoColor: true})
for k, v := range outputs {
_, s := sensitive[k]
Expand Down Expand Up @@ -360,6 +364,9 @@ func (b *TFBlueprintTest) GetTFSetupOutputListVal(key string) []string {
if b.setupDir == "" {
b.t.Fatal("Setup path not set")
}
// allow only parallel reads as Terraform plugin cache isn't concurrent safe
rUnlockFn := b.rLockFn()
defer rUnlockFn()
return terraform.OutputList(b.t, &terraform.Options{TerraformDir: b.setupDir, Logger: b.logger, NoColor: true}, key)
}

Expand All @@ -372,6 +379,9 @@ func (b *TFBlueprintTest) GetTFSetupStringOutput(key string) string {
if b.setupDir == "" {
b.t.Fatal("Setup path not set")
}
// allow only parallel reads as Terraform plugin cache isn't concurrent safe
rUnlockFn := b.rLockFn()
defer rUnlockFn()
return terraform.Output(b.t, &terraform.Options{TerraformDir: b.setupDir, Logger: b.logger, NoColor: true}, key)
}

Expand Down Expand Up @@ -469,8 +479,14 @@ func (b *TFBlueprintTest) DefaultInit(assert *assert.Assertions) {

// Vet runs TF plan, TF show, and gcloud terraform vet on a blueprint.
func (b *TFBlueprintTest) Vet(assert *assert.Assertions) {
vetTempDir, err := os.MkdirTemp(os.TempDir(), "btp")
if err != nil {
b.t.Fatalf("Temp directory %q could not created: %v", vetTempDir, err)
}
defer os.RemoveAll(vetTempDir)

localOptions := b.GetTFOptions()
localOptions.PlanFilePath = filepath.Join(os.TempDir(), "plan.tfplan")
localOptions.PlanFilePath = filepath.Join(vetTempDir, vetFilename)
terraform.Plan(b.t, localOptions)
jsonPlan := terraform.Show(b.t, localOptions)
filepath, err := utils.WriteTmpFileWithExtension(jsonPlan, "json")
Expand Down Expand Up @@ -506,42 +522,24 @@ func (b *TFBlueprintTest) Init(assert *assert.Assertions) {
// Apply runs the default or custom apply function for the blueprint.
func (b *TFBlueprintTest) Apply(assert *assert.Assertions) {
// allow only parallel reads as Terraform plugin cache isn't concurrent safe
if err := b.tftCacheMutex.RLock(); err != nil {
b.t.Fatalf("Could not acquire read lock: %v", err)
}
defer func() {
if err := b.tftCacheMutex.RUnlock(); err != nil {
b.t.Fatalf("Could not release read lock: %v", err)
}
}()
rUnlockFn := b.rLockFn()
defer rUnlockFn()
b.apply(assert)
}

// Verify runs the default or custom verify function for the blueprint.
func (b *TFBlueprintTest) Verify(assert *assert.Assertions) {
// allow only parallel reads as Terraform plugin cache isn't concurrent safe
if err := b.tftCacheMutex.RLock(); err != nil {
b.t.Fatalf("Could not acquire read lock: %v", err)
}
defer func() {
if err := b.tftCacheMutex.RUnlock(); err != nil {
b.t.Fatalf("Could not release read lock: %v", err)
}
}()
rUnlockFn := b.rLockFn()
defer rUnlockFn()
b.verify(assert)
}

// Teardown runs the default or custom teardown function for the blueprint.
func (b *TFBlueprintTest) Teardown(assert *assert.Assertions) {
// allow only parallel reads as Terraform plugin cache isn't concurrent safe
if err := b.tftCacheMutex.RLock(); err != nil {
b.t.Fatalf("Could not acquire read lock:%v", err)
}
defer func() {
if err := b.tftCacheMutex.RUnlock(); err != nil {
b.t.Fatalf("Could not release read lock: %v", err)
}
}()
rUnlockFn := b.rLockFn()
defer rUnlockFn()
b.teardown(assert)
}

Expand Down Expand Up @@ -594,3 +592,16 @@ func (b *TFBlueprintTest) RedeployTest(n int, nVars map[int]map[string]interface
utils.RunStage("verify", func() { b.Verify(a) })
}
}

// rLockFn sets a read mutex lock, and returns the corresponding unlock function
func (b *TFBlueprintTest) rLockFn() func() {
if err := b.tftCacheMutex.RLock(); err != nil {
b.t.Fatalf("Could not acquire read lock:%v", err)
}

return func() {
if err := b.tftCacheMutex.RUnlock(); err != nil {
b.t.Fatalf("Could not release read lock: %v", err)
}
}
}

0 comments on commit 53ab9bc

Please sign in to comment.