diff --git a/src/common/json/json_writer.go b/src/common/json/json_writer.go index f580a987d..11d7babef 100644 --- a/src/common/json/json_writer.go +++ b/src/common/json/json_writer.go @@ -1,6 +1,7 @@ package json import ( + "bytes" "encoding/json" "fmt" "math" @@ -89,6 +90,7 @@ func AddTagsToResourceStr(fullOriginStr string, resourceBlock structure.IBlock, firstTagIndex := strings.Index(tagsStr[1:], "{") + 2 firstTagStr := tagsStr[firstTagIndex : firstTagIndex+strings.Index(tagsStr[firstTagIndex+1:], "\"")] tagEntryIndent := findIndent(tagsStr, '"', strings.Index(tagsStr[1:], "{")) // find the indent of the key and value entry + compact := false if strings.Contains(firstTagStr, "\n") { // If the tag string has a newline, it means the indent needs to be re-evaluated. Example for this use case: // "Tags": [ @@ -100,6 +102,9 @@ func AddTagsToResourceStr(fullOriginStr string, resourceBlock structure.IBlock, indentDiff := len(tagEntryIndent) - len(tagBlockIndent) tagBlockIndent = tagBlockIndent[0 : len(tagBlockIndent)-indentDiff] tagEntryIndent = tagEntryIndent[0 : len(tagEntryIndent)-indentDiff] + } else if len(tagsLinesList) == 1 { + // multi tags in one line + compact = true } else { // Otherwise, need to take the indent of the "{" character. This case handles: // "Tags": [ @@ -110,14 +115,27 @@ func AddTagsToResourceStr(fullOriginStr string, resourceBlock structure.IBlock, // unmarshal updated tags with the indent matching origin file. This will create the tags with the `[]` wrapping which will be discarded later strAddedTags, err := json.MarshalIndent(diff.Added, tagBlockIndent, strings.TrimPrefix(tagEntryIndent, tagBlockIndent)) - netNewTagLines := strings.Split(string(strAddedTags), "\n") - finalTagsStr := strings.Join(tagsLinesList[:len(tagsLinesList)-1], "\n") + ",\n" + - strings.Join(netNewTagLines[1:len(netNewTagLines)-1], "\n") + "\n" + - tagsLinesList[len(tagsLinesList)-1] - _ = finalTagsStr if err != nil { logger.Warning(fmt.Sprintf("failed to unmarshal tags %s with indent '%s' because of error: %s", diff.Added, tagBlockIndent, err)) } + + finalTagsStr := "" + if compact { + dst := &bytes.Buffer{} + if err := json.Compact(dst, strAddedTags); err != nil { + logger.Warning(fmt.Sprintf("failed to build tags %s with err: %s", strAddedTags, err)) + return fullOriginStr + } + tagsLine := tagsLinesList[0] + + finalTagsStr = tagsLine[:len(tagsLine)-1] + "," + dst.String()[1:] + } else { + netNewTagLines := strings.Split(string(strAddedTags), "\n") + finalTagsStr = strings.Join(tagsLinesList[:len(tagsLinesList)-1], "\n") + ",\n" + + strings.Join(netNewTagLines[1:len(netNewTagLines)-1], "\n") + "\n" + + tagsLinesList[len(tagsLinesList)-1] + + } tagsStartRelativeToResource := tagBrackets.Open.CharIndex - resourceBrackets.Open.CharIndex tagsEndRelativeToResource := tagBrackets.Close.CharIndex - resourceBrackets.Open.CharIndex diff --git a/src/common/json/json_writer_test.go b/src/common/json/json_writer_test.go index d9fadafc3..8b072fb8e 100644 --- a/src/common/json/json_writer_test.go +++ b/src/common/json/json_writer_test.go @@ -503,4 +503,8 @@ func TestCFNWriting(t *testing.T) { directory := "../../../tests/cloudformation/resources/single_line" writeJSONTestHelper(t, directory, "cfn", []tags.Tag{{Key: "old_tag1", Value: "old_val1"}, {Key: "old_tag2", Value: "old_val2"}}) }) + t.Run("test CFN writing tagged single line multi tag", func(t *testing.T) { + directory := "../../../tests/cloudformation/resources/single_line" + writeJSONTestHelper(t, directory, "multiTag", []tags.Tag{{Key: "old_tag1", Value: "old_val1"}, {Key: "old_tag2", Value: "old_val2"}}) + }) } diff --git a/tests/cloudformation/resources/single_line/multiTag.json b/tests/cloudformation/resources/single_line/multiTag.json new file mode 100644 index 000000000..41ff9467e --- /dev/null +++ b/tests/cloudformation/resources/single_line/multiTag.json @@ -0,0 +1,32 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "This Template will create VPC Peering Connection", + "Parameters": { + "EDHPrivateSubnetAAZ": { + "Description": "Availability Zone of the instance", + "Type": "String", + "AllowedPattern": ".+" + }, + "SecurityGroupID": { + "Description": "Security Group ID", + "Type": "String", + "AllowedPattern": ".+" + }, + "SubnetID": { + "Description": "Subnet ID", + "Type": "String", + "AllowedPattern": ".+" + } + }, + "Resources": { + "Peer" : { + "Type" : "AWS::EC2::VPCPeeringConnection", + "Properties" : { + "PeerVpcId" : "vpc-6da4bb14", + "Tags" : [{"Key" : "Name", "Value" : "Dev-Uat-Peering-Connection"},{"Key" : "gafg:billing", "Value" : "edh"}], + "VpcId" : "vpc-1993c37f", + "PeerOwnerId" : "950826683103" + } + } + } +} \ No newline at end of file diff --git a/tests/cloudformation/resources/single_line/multiTag_expected.json b/tests/cloudformation/resources/single_line/multiTag_expected.json new file mode 100644 index 000000000..cf0b5f610 --- /dev/null +++ b/tests/cloudformation/resources/single_line/multiTag_expected.json @@ -0,0 +1,32 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "This Template will create VPC Peering Connection", + "Parameters": { + "EDHPrivateSubnetAAZ": { + "Description": "Availability Zone of the instance", + "Type": "String", + "AllowedPattern": ".+" + }, + "SecurityGroupID": { + "Description": "Security Group ID", + "Type": "String", + "AllowedPattern": ".+" + }, + "SubnetID": { + "Description": "Subnet ID", + "Type": "String", + "AllowedPattern": ".+" + } + }, + "Resources": { + "Peer" : { + "Type" : "AWS::EC2::VPCPeeringConnection", + "Properties" : { + "PeerVpcId" : "vpc-6da4bb14", + "Tags" : [{"Key" : "Name", "Value" : "Dev-Uat-Peering-Connection"},{"Key" : "gafg:billing", "Value" : "edh"},{"Key":"new_tag","Value":"new_value"},{"Key":"another_tag","Value":"another_val"}], + "VpcId" : "vpc-1993c37f", + "PeerOwnerId" : "950826683103" + } + } + } +} \ No newline at end of file