Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON output fetching fixes #1462

Merged
merged 15 commits into from
Dec 10, 2024
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ env: &env
TERRAFORM_VERSION: 1.5.7
TOFU_VERSION: 1.8.0
PACKER_VERSION: 1.10.0
TERRAGRUNT_VERSION: v0.52.0
TERRAGRUNT_VERSION: v0.68.7
OPA_VERSION: v0.33.1
GO_VERSION: 1.21.1
GO111MODULE: auto
Expand Down
13 changes: 12 additions & 1 deletion modules/terraform/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ const (

// TerraformDefaultPath to run terraform
TerraformDefaultPath = "terraform"

// TerragruntDefaultPath to run terragrunt
TerragruntDefaultPath = "terragrunt"
)

var DefaultExecutable = defaultTerraformExecutable()
Expand All @@ -49,8 +52,16 @@ func GetCommonOptions(options *Options, args ...string) (*Options, []string) {
options.TerraformBinary = DefaultExecutable
}

if options.TerraformBinary == "terragrunt" {
if options.TerraformBinary == TerragruntDefaultPath {
args = append(args, "--terragrunt-non-interactive")
// for newer Terragrunt version, setting simplified log formatting
if options.EnvVars == nil {
options.EnvVars = map[string]string{}
}
_, tgLogSet := options.EnvVars["TERRAGRUNT_DISABLE_LOG_FORMATTING"]
if !tgLogSet {
options.EnvVars["TERRAGRUNT_DISABLE_LOG_FORMATTING"] = "true"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This env var is deprecated, please use TERRAGRUNT_LOG_FORMAT=key-value

*But only if you use the latest version that supports log format.

}
}

if options.Parallelism > 0 && len(args) > 0 && collections.ListContains(commandsWithParallelism, args[0]) {
Expand Down
45 changes: 44 additions & 1 deletion modules/terraform/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@ import (
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"

"github.com/gruntwork-io/terratest/modules/testing"
"github.com/stretchr/testify/require"
)

const skipJsonLogLine = " msg="

var (
ansiLineRegex = regexp.MustCompile(`(?m)^\x1b\[[0-9;]*m.*`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we add comments on what these regex mean in plain English?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

tgLogLevel = regexp.MustCompile(`.*time=\S+ level=\S+ prefix=\S+ binary=\S+ msg=.*`)
Copy link

@levkohimins levkohimins Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use TERRAGRUNT_LOG_CUSTOM_FORMAT="%msg(color=disable)" to get rid of cleanJson function at all.

*But only if you use the latest version that supports custom log format.

Copy link
Member Author

@denis256 denis256 Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Output still has lines like:

time=2024-12-05UTC17:32:10Z level=stdout tf-path=terraform msg=Initializing the backend...

which requires cleaning when parsing output

)

// Output calls terraform output for the given variable and return its string value representation.
// It only designed to work with primitive terraform types: string, number and bool.
// Please use OutputStruct for anything else.
Expand Down Expand Up @@ -279,7 +288,11 @@ func OutputJsonE(t testing.TestingT, options *Options, key string) (string, erro
args = append(args, key)
}

return RunTerraformCommandAndGetStdoutE(t, options, args...)
rawJson, err := RunTerraformCommandAndGetStdoutE(t, options, args...)
if err != nil {
return rawJson, err
}
return cleanJson(rawJson)
}

// OutputStruct calls terraform output for the given variable and stores the
Expand Down Expand Up @@ -348,3 +361,33 @@ func OutputAll(t testing.TestingT, options *Options) map[string]interface{} {
func OutputAllE(t testing.TestingT, options *Options) (map[string]interface{}, error) {
return OutputForKeysE(t, options, nil)
}

// clean the ANSI characters from the JSON and update formating
func cleanJson(input string) (string, error) {
// Remove ANSI escape codes
cleaned := ansiLineRegex.ReplaceAllString(input, "")
cleaned = tgLogLevel.ReplaceAllString(cleaned, "")

lines := strings.Split(cleaned, "\n")
var result []string
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed != "" && !strings.Contains(trimmed, skipJsonLogLine) {
result = append(result, trimmed)
}
}
ansiClean := strings.Join(result, "\n")

var jsonObj interface{}
if err := json.Unmarshal([]byte(ansiClean), &jsonObj); err != nil {
return "", err
}

// Format JSON output with indentation
normalized, err := json.MarshalIndent(jsonObj, "", " ")
if err != nil {
return "", err
}

return string(normalized), nil
}
66 changes: 66 additions & 0 deletions modules/terraform/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package terraform

import (
"fmt"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"

"github.com/gruntwork-io/terratest/modules/files"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -31,6 +34,40 @@ func TestOutputString(t *testing.T) {

num1 := Output(t, options, "number1")
require.Equal(t, num1, "3", "Number %q should match %q", "3", num1)

unicodeString := Output(t, options, "unicode_string")
require.Equal(t, "söme chäräcter", unicodeString)
}

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

testFolder, err := files.CopyTerraformFolderToTemp("../../test/fixtures/terraform-output", t.Name())
require.NoError(t, err)

WriteFile(t, filepath.Join(testFolder, "terragrunt.hcl"), []byte{})

options := &Options{
TerraformDir: testFolder,
TerraformBinary: "terragrunt",
}

InitAndApply(t, options)

b := Output(t, options, "bool")
require.Equal(t, b, "true", "Bool %q should match %q", "true", b)

str := Output(t, options, "string")
require.Equal(t, str, "This is a string.", "String %q should match %q", "This is a string.", str)

num := Output(t, options, "number")
require.Equal(t, num, "3.14", "Number %q should match %q", "3.14", num)

num1 := Output(t, options, "number1")
require.Equal(t, num1, "3", "Number %q should match %q", "3", num1)

unicodeString := Output(t, options, "unicode_string")
require.Equal(t, "söme chäräcter", unicodeString)
}

func TestOutputList(t *testing.T) {
Expand Down Expand Up @@ -310,6 +347,11 @@ func TestOutputJson(t *testing.T) {
"sensitive": false,
"type": "string",
"value": "This is a string."
},
"unicode_string": {
"sensitive": false,
"type": "string",
"value": "söme chäräcter"
}
}`

Expand Down Expand Up @@ -433,3 +475,27 @@ func TestOutputsForKeysError(t *testing.T) {

require.Error(t, err)
}

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

testFolder, err := files.CopyTerraformFolderToTemp("../../test/fixtures/terraform-output-map", t.Name())
require.NoError(t, err)

WriteFile(t, filepath.Join(testFolder, "terragrunt.hcl"), []byte{})

options := &Options{
TerraformDir: testFolder,
TerraformBinary: "terragrunt",
}

InitAndApply(t, options)

output, err := OutputAllE(t, options)

require.NoError(t, err)
assert.NotNil(t, output)
assert.NotEmpty(t, output)
assert.Contains(t, output, "mogwai")
assert.Equal(t, "söme chäräcter", output["not_a_map_unicode"])
}
3 changes: 3 additions & 0 deletions test/fixtures/terraform-output-map/output.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ output "not_a_map" {
value = "This is not a map."
}

output "not_a_map_unicode" {
value = "söme chäräcter"
}
4 changes: 4 additions & 0 deletions test/fixtures/terraform-output/output.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ output "number" {
output "number1" {
value = 3
}

output "unicode_string" {
value = "söme chäräcter"
}