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

Fix an error when trying to load default variables #719

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 5 additions & 98 deletions util/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ package util
import (
"fmt"
"os"
"path"
"regexp"
"strings"
"sync"

"github.com/coveooss/gotemplate/v3/collections"
"github.com/coveooss/gotemplate/v3/json"
"github.com/coveooss/gotemplate/v3/template"
"github.com/coveooss/gotemplate/v3/utils"
"github.com/coveooss/gotemplate/v3/yaml"
"github.com/coveooss/multilogger"
"github.com/coveooss/multilogger/errors"
Expand All @@ -29,10 +26,7 @@ type (
}
)

var (
reTfVariables = regexp.MustCompile(`(?ms)^variable\s+".*?"\s+{(\s*\n.*?^}|\s*}$)`)
cacheDefault sync.Map
)
var cacheDefault sync.Map

// LoadDefaultValues returns a map of the variables defined in the tfvars file
func LoadDefaultValues(folder string, logger *multilogger.Logger, keepInCache bool) (importedVariables map[string]interface{}, allVariables map[string]*tfconfig.Variable, err error) {
Expand All @@ -51,7 +45,7 @@ func LoadDefaultValues(folder string, logger *multilogger.Logger, keepInCache bo
return cached.values, cached.all, nil
}

return loadDefaultValues(folder, true, logger)
return loadDefaultValues(folder, logger)
}

// LoadVariablesFromHcl parses HCL content to get a map of the attributes
Expand Down Expand Up @@ -162,9 +156,9 @@ func LoadVariablesFromSource(content, fileName, cwd string, applyTemplate bool,
return
}

func loadDefaultValues(folder string, retry bool, logger *multilogger.Logger) (map[string]interface{}, map[string]*tfconfig.Variable, error) {
func loadDefaultValues(folder string, logger *multilogger.Logger) (map[string]interface{}, map[string]*tfconfig.Variable, error) {
module, diags := tfconfig.LoadModule(folder)
if diags.HasErrors() {
if diags.HasErrors() && logger != nil {
var err errors.Array
for _, diag := range diags {
if diag.Pos != nil && diag.Pos.Filename != "" {
Expand All @@ -174,21 +168,8 @@ func loadDefaultValues(folder string, retry bool, logger *multilogger.Logger) (m
err = append(err, fmt.Errorf("(%c) %s, %s", diag.Severity, diag.Summary, diag.Detail))
}
}
if !retry {
return nil, nil, err
}

// If we get an error while loading variables, we try to isolate variables definition in a temporary folder.
if logger != nil {
logger.Debugf("First try with terraform native LoadConfigDir failed\n%v", color.HiBlackString(err.Error()))
logger.Debug("Retrying with a temporary folder containing only variables definitions")
}
tmpDir, errTmpDir := createTerraformVariablesTemporaryFolder(folder)
if tmpDir == "" || errTmpDir != nil {
return nil, nil, append(err, errTmpDir)
}
defer func() { os.RemoveAll(tmpDir) }()
return loadDefaultValues(tmpDir, false, logger)
logger.Debugf("Ignored error while trying to load the default variables:\n%v", color.HiBlackString(err.Error()))
}

variablesMap := map[string]interface{}{}
Expand All @@ -203,77 +184,3 @@ func loadDefaultValues(folder string, retry bool, logger *multilogger.Logger) (m
}
return variablesMap, module.Variables, nil
}

func isOverride(filename string) bool {
return path.Base(filename) == "override.tf" ||
path.Base(filename) == "override.tf.json" ||
strings.HasSuffix(filename, "_override.tf") ||
strings.HasSuffix(filename, "_override.tf.json")
}

// Create a temporary folder containing only the terraform code used to declare and initialize variables.
// This allows us to load the default variable value to make them available to gotemplate code before we
// apply gotemplate on terraform file. If we don't, any terraform code that is not considered as valid
// code will cause an error and make LoadDefaultValues fail.
func createTerraformVariablesTemporaryFolder(folder string) (tmpDir string, err error) {
var files []string

if _, err = os.Stat(folder); err != nil {
return
}
if files, err = utils.FindFiles(folder, false, false, "*.tf", "*.tf.json"); err != nil {
return
}

// The variables are spitted up into 4 different files depending if they are declared
// in a .tf file or a .tf.json file and if they are declared in an override file or not.
generated := make(map[string]interface{})
for _, filename := range files {
name, ext := "regular", path.Ext(filename)
if isOverride(filename) {
name = "override"
}

var content []byte
if content, err = os.ReadFile(filename); err != nil {
return
}

if ext == ".tf" {
if found := reTfVariables.FindAllString(string(content), -1); len(found) > 0 {
currentValue, _ := generated[name+ext].(string)
generated[name+ext] = currentValue + fmt.Sprintf("// %s\n%s\n", filename, strings.Join(found, "\n\n"))
}
} else {
ext = ".tf.json"
var jsonContent json.Dictionary
if err = json.Unmarshal(content, &jsonContent); err == nil {
variables := jsonContent.Clone("variable")
if variables.Len() > 0 {
currentValue, _ := generated[name+ext].(json.Dictionary)
generated[name+ext] = variables.Merge(currentValue)
}
}
}
}

if len(generated) > 0 {
// We write the resulting files
if tmpDir, err = os.MkdirTemp("", "load_defaults"); err != nil {
return
}
for filename, value := range generated {
var content []byte
switch value := value.(type) {
case string:
content = []byte(value)
case json.Dictionary:
content = []byte(value.PrettyPrint())
}
if err = os.WriteFile(path.Join(tmpDir, filename), content, 0644); err != nil {
return
}
}
}
return
}
4 changes: 2 additions & 2 deletions util/terraform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ func TestLoadDefaultValues(t *testing.T) {
wantResult map[string]interface{}
err string
}{
{"All Types",
{
"All Types",
testFixtureDefaultValues,
map[string]interface{}{
"a": "A (a.tf)",
Expand All @@ -34,7 +35,6 @@ func TestLoadDefaultValues(t *testing.T) {
},
"",
},
{"Invalid Folder", "Invalid", nil, "Module directory Invalid does not exist or cannot be read"},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
Expand Down