helper/utils.go (196 lines of code) (raw):

package helper import ( "encoding/json" "fmt" "io/fs" "os" "path" "strings" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2/hclwrite" ctyJson "github.com/zclconf/go-cty/cty/json" ) // IsArrayWithSameValue returns true if all elements in `arr` are identical func IsArrayWithSameValue(arr []string) bool { for _, x := range arr { if x != arr[0] { return false } } return true } // Prefix returns the longest common prefix of all elements in `arr` func Prefix(arr []string) string { if len(arr) == 0 { return "" } index := 0 for index = 0; index < len(arr[0]); index++ { match := true for i := 1; i < len(arr); i++ { if index >= len(arr[i]) || arr[i][index] != arr[0][index] { match = false break } } if !match { break } } return arr[0][0:index] } // Suffix returns the longest common suffix of all elements in `arr` func Suffix(arr []string) string { if len(arr) == 0 { return "" } index := 0 for index = 1; index <= len(arr[0]); index++ { match := true for i := 1; i < len(arr); i++ { if index > len(arr[i]) || arr[i][len(arr[i])-index] != arr[0][len(arr[0])-index] { match = false break } } if !match { break } } return arr[0][len(arr[0])-index+1:] } func ListHclFiles(workingDirectory string) []fs.DirEntry { res := make([]fs.DirEntry, 0) files, err := os.ReadDir(workingDirectory) if err != nil { return res } for _, file := range files { if strings.HasSuffix(file.Name(), ".tf") { res = append(res, file) } } return res } func ListHclBlocks(workingDirectory string) []*hclwrite.Block { res := make([]*hclwrite.Block, 0) files := ListHclFiles(workingDirectory) for _, file := range files { filePath := path.Join(workingDirectory, file.Name()) // #nosec G304 f, err := os.ReadFile(filePath) if err != nil { continue } hclFile, diags := hclwrite.ParseConfig(f, file.Name(), hcl.InitialPos) if diags.HasErrors() { continue } res = append(res, hclFile.Body().Blocks()...) } return res } func FindHclBlock(workingDirectory string, blockType string, labels []string) *hclwrite.Block { blocks := ListHclBlocks(workingDirectory) for _, block := range blocks { if block.Type() != blockType { continue } if len(block.Labels()) != len(labels) { continue } isLabelsEqual := true for i, label := range labels { if block.Labels()[i] != label { isLabelsEqual = false break } } if isLabelsEqual { return block } } return nil } // GetTokensForExpression convert a literal value to hclwrite.Tokens func GetTokensForExpression(expression string) hclwrite.Tokens { syntaxTokens, diags := hclsyntax.LexConfig([]byte(expression), "main.tf", hcl.InitialPos) if diags.HasErrors() { return nil } res := make([]*hclwrite.Token, 0) for _, token := range syntaxTokens { res = append(res, &hclwrite.Token{ Type: token.Type, Bytes: token.Bytes, }) } return res } // ParseHclArray parse `attrValue` to an array, example `attrValue` `["a", "b", 0]` will return ["\"a\"", "\"b\"", "0"] func ParseHclArray(attrValue string) []string { if strings.HasPrefix(attrValue, "[") && strings.HasSuffix(attrValue, "]") { arr := strings.Split(attrValue[1:len(attrValue)-1], ",") for i := range arr { arr[i] = strings.TrimSpace(arr[i]) } return arr } return nil } // ToHclSearchReplace generates hcl expression from `input` func ToHclSearchReplace(input interface{}, search []string, replacement []string) (string, bool) { found := false switch value := input.(type) { case []interface{}: if len(value) == 0 { return "[]", false } res := make([]string, 0) for _, element := range value { config, ok := ToHclSearchReplace(element, search, replacement) found = found || ok res = append(res, config) } return fmt.Sprintf("[\n%s\n]", strings.Join(res, ",\n")), found case map[string]interface{}: if len(value) == 0 { return "{}", found } attrs := make([]string, 0) for k, v := range value { if v == nil { attrs = append(attrs, fmt.Sprintf("%s = null", quotedKey(k))) continue } config, ok := ToHclSearchReplace(v, search, replacement) found = found || ok attrs = append(attrs, fmt.Sprintf("%s = %s", quotedKey(k), config)) } return fmt.Sprintf("{\n%s\n}", strings.Join(attrs, "\n")), found case string: for i := range search { if search[i] == value { return replacement[i], true } } return fmt.Sprintf(`"%s"`, strings.ReplaceAll(value, "\"", "\\\"")), false default: return fmt.Sprintf("%v", value), false } } func GetValueFromExpression(tokens hclwrite.Tokens) interface{} { expression, _ := hclsyntax.ParseExpression(tokens.Bytes(), "", hcl.InitialPos) if value, dialog := expression.Value(&hcl.EvalContext{}); dialog == nil || !dialog.HasErrors() { if data, err := ctyJson.Marshal(value, value.Type()); err == nil { var input interface{} if err = json.Unmarshal(data, &input); err == nil { return input } } } return nil } func quotedKey(input string) string { if len(input) == 0 { return input } if strings.Contains(input, ".") || strings.Contains(input, "/") || input[0] == '$' || input[0] >= '0' && input[0] <= '9' { return fmt.Sprintf("\"%s\"", input) } return input }