Skip to content

Commit

Permalink
Merge pull request #1426 from gruntwork-io/fail-on-warning
Browse files Browse the repository at this point in the history
Allow to fail on specific warning messages
  • Loading branch information
james03160927 authored Aug 5, 2024
2 parents 5a79983 + 337c0c7 commit 9ffe143
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 2 deletions.
68 changes: 68 additions & 0 deletions modules/terraform/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package terraform

import (
"path/filepath"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -63,6 +64,73 @@ func TestApplyWithErrorWithRetry(t *testing.T) {

require.Contains(t, out, "This is the first run, exiting with an error")
}

func TestApplyWithWarning(t *testing.T) {
scenarios := []struct {
name string
folder string
isError bool
warnings map[string]string
}{
{
name: "Warning",
folder: "../../test/fixtures/terraform-with-warning",
isError: true,
warnings: map[string]string{
"lorem ipsum": "lorem ipsum warning",
},
},
{
name: "WarningNotMatch",
folder: "../../test/fixtures/terraform-with-warning",
isError: false,
warnings: map[string]string{
"lorem ipsum dolor sit amet": "some warning",
},
},
{
name: "Error",
folder: "../../test/fixtures/terraform-with-error",
isError: true,
warnings: map[string]string{
"lorem ipsum": "lorem ipsum warning",
},
},
{
name: "NoError",
folder: "../../test/fixtures/terraform-no-error",
isError: false,
warnings: map[string]string{
"lorem ipsum": "lorem ipsum warning",
},
},
}

for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.name, func(t *testing.T) {
t.Parallel()

testFolder, err := files.CopyTerraformFolderToTemp(scenario.folder, strings.Replace(t.Name(), "/", "-", -1))
require.NoError(t, err)

options := WithDefaultRetryableErrors(t, &Options{
TerraformDir: testFolder,
NoColor: true,
WarningsAsErrors: scenario.warnings,
})

out, err := InitAndApplyE(t, options)
if scenario.isError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.NotEmpty(t, out)
})
}
}

func TestTgApplyAllTgError(t *testing.T) {
t.Parallel()

Expand Down
38 changes: 36 additions & 2 deletions modules/terraform/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package terraform
import (
"fmt"
"os/exec"
"regexp"
"strings"

"github.com/gruntwork-io/terratest/modules/collections"
"github.com/gruntwork-io/terratest/modules/retry"
Expand Down Expand Up @@ -81,9 +83,18 @@ func RunTerraformCommandE(t testing.TestingT, additionalOptions *Options, additi

cmd := generateCommand(options, args...)
description := fmt.Sprintf("%s %v", options.TerraformBinary, args)

return retry.DoWithRetryableErrorsE(t, description, options.RetryableTerraformErrors, options.MaxRetries, options.TimeBetweenRetries, func() (string, error) {
return shell.RunCommandAndGetOutputE(t, cmd)
s, err := shell.RunCommandAndGetOutputE(t, cmd)
if err != nil {
return s, err
}
if err := hasWarning(additionalOptions, s); err != nil {
return s, err
}
return s, err
})

}

// RunTerraformCommandAndGetStdoutE runs terraform with the given arguments and options and returns solely its stdout
Expand All @@ -94,7 +105,14 @@ func RunTerraformCommandAndGetStdoutE(t testing.TestingT, additionalOptions *Opt
cmd := generateCommand(options, args...)
description := fmt.Sprintf("%s %v", options.TerraformBinary, args)
return retry.DoWithRetryableErrorsE(t, description, options.RetryableTerraformErrors, options.MaxRetries, options.TimeBetweenRetries, func() (string, error) {
return shell.RunCommandAndGetStdOutE(t, cmd)
s, err := shell.RunCommandAndGetOutputE(t, cmd)
if err != nil {
return s, err
}
if err := hasWarning(additionalOptions, s); err != nil {
return s, err
}
return s, err
})
}

Expand Down Expand Up @@ -137,3 +155,19 @@ func defaultTerraformExecutable() string {
// fallback to Tofu if terraform is not available
return TofuDefaultPath
}

func hasWarning(opts *Options, out string) error {
for k, v := range opts.WarningsAsErrors {
str := fmt.Sprintf("\nWarning: %s[^\n]*\n", k)
re, err := regexp.Compile(str)
if err != nil {
return fmt.Errorf("cannot compile regex for warning detection: %w", err)
}
m := re.FindAllString(out, -1)
if len(m) == 0 {
continue
}
return fmt.Errorf("warning(s) were found: %s:\n%s", v, strings.Join(m, ""))
}
return nil
}
5 changes: 5 additions & 0 deletions modules/terraform/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type Options struct {
PlanFilePath string // The path to output a plan file to (for the plan command) or read one from (for the apply command)
PluginDir string // The path of downloaded plugins to pass to the terraform init command (-plugin-dir)
SetVarsAfterVarFiles bool // Pass -var options after -var-file options to Terraform commands
WarningsAsErrors map[string]string // Terraform warning messages that should be treated as errors. The keys are a regexp to match against the warning and the value is what to display to a user if that warning is matched.
}

// Clone makes a deep copy of most fields on the Options object and returns it.
Expand Down Expand Up @@ -99,6 +100,10 @@ func (options *Options) Clone() (*Options, error) {
for key, val := range options.RetryableTerraformErrors {
newOptions.RetryableTerraformErrors[key] = val
}
newOptions.WarningsAsErrors = make(map[string]string)
for key, val := range options.WarningsAsErrors {
newOptions.WarningsAsErrors[key] = val
}

return newOptions, nil
}
Expand Down
22 changes: 22 additions & 0 deletions test/fixtures/terraform-with-warning/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
terraform {
required_providers {
validation = {
source = "tlkamp/validation"
version = "1.1.1"
}
null = {
source = "hashicorp/null"
version = "3.2.2"
}
}
}

# this data source will produce warning when `condition` is evaluated to `true`
data "validation_warning" "warn" {
for_each = toset([for i in range(10) : format("%02d", i)])
condition = true
summary = "lorem ipsum ${each.value}"
details = "lorem ipsum dolor sit amet"
}

resource "null_resource" "empty" {}

0 comments on commit 9ffe143

Please sign in to comment.