fix: follow newline conventions of the autofixed file

This change makes the autofix handler use the newline separator defined
in the fixed file for writing its changes.
This commit is contained in:
Vlad Klokun
2023-01-12 19:25:39 +02:00
parent 02720d32dd
commit b02410184e
8 changed files with 187 additions and 8 deletions

View File

@@ -1,6 +1,9 @@
package fixhandler
import (
"sort"
"strings"
"github.com/armosec/armoapi-go/armotypes"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/opa-utils/reporthandling"
@@ -43,7 +46,7 @@ type fixInfoMetadata struct {
linesToRemove *[]linesToRemove
}
// ContentToAdd holds the information about where to insert the new changes in the existing yaml file
// contentToAdd holds the information about where to insert the new changes in the existing yaml file
type contentToAdd struct {
// Line where the fix should be applied to
line int
@@ -51,6 +54,49 @@ type contentToAdd struct {
content string
}
func withNewline(content, targetNewline string) string {
replaceNewlines := map[string]bool{
unixNewline: true,
windowsNewline: true,
oldMacNewline: true,
}
replaceNewlines[targetNewline] = false
newlinesToReplace := make([]string, len(replaceNewlines))
i := 0
for k := range replaceNewlines {
newlinesToReplace[i] = k
i++
}
// To ensure that we fully replace Windows newlines (CR LF), and not
// corrupt them into two new newlines (CR CR or LF LF) by partially
// replacing either CR or LF, we have to ensure we replace longer
// Windows newlines first
sort.Slice(newlinesToReplace, func(i int, j int) bool {
return len(newlinesToReplace[i]) > len(newlinesToReplace[j])
})
// strings.Replacer takes a flat list of (oldVal, newVal) pairs, so we
// need to allocate twice the space and assign accordingly
newlinesOldNew := make([]string, 2*len(replaceNewlines))
i = 0
for _, nl := range newlinesToReplace {
newlinesOldNew[2*i] = nl
newlinesOldNew[2*i+1] = targetNewline
i++
}
replacer := strings.NewReplacer(newlinesOldNew...)
return replacer.Replace(content)
}
// Content returns the content that will be added, separated by the explicitly
// provided `targetNewline`
func (c *contentToAdd) Content(targetNewline string) string {
return withNewline(c.content, targetNewline)
}
// LinesToRemove holds the line numbers to remove from the existing yaml file
type linesToRemove struct {
startLine int

View File

@@ -0,0 +1,89 @@
package fixhandler
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestContentNewlinesMatchTarget(t *testing.T) {
cases := []struct {
Name string
InputContent string
TargetNewline string
WantedContent string
}{
{
"Unix to DOS",
"first line\nsecond line\n",
"\r\n",
"first line\r\nsecond line\r\n",
},
{
"Unix to Unix",
"first line\nsecond line\n",
"\n",
"first line\nsecond line\n",
},
{
"Unix to Mac",
"first line\nsecond line\n",
"\r",
"first line\rsecond line\r",
},
{
"DOS to Unix",
"first line\r\nsecond line\r\n",
"\n",
"first line\nsecond line\n",
},
{
"DOS to DOS",
"first line\r\nsecond line\r\n",
"\r\n",
"first line\r\nsecond line\r\n",
},
{
"DOS to OldMac",
"first line\r\nsecond line\r\n",
"\r",
"first line\rsecond line\r",
},
{
"Mac to DOS",
"first line\rsecond line\r",
"\r\n",
"first line\r\nsecond line\r\n",
},
{
"Mac to Unix",
"first line\rsecond line\r",
"\n",
"first line\nsecond line\n",
},
{
"DOS, Mac to Unix",
"first line\r\n\rsecond line\r",
"\n",
"first line\n\nsecond line\n",
},
{
"Mac, DOS to Unix",
"first line\r\r\r\nsecond line\r",
"\n",
"first line\n\n\nsecond line\n",
},
}
for _, tc := range cases {
t.Run(tc.Name, func(t *testing.T) {
c := &contentToAdd{content: tc.InputContent}
want := tc.WantedContent
got := c.Content(tc.TargetNewline)
assert.Equal(t, want, got)
})
}
}

View File

@@ -24,6 +24,10 @@ import (
const UserValuePrefix = "YOUR_"
const windowsNewline = "\r\n"
const unixNewline = "\n"
const oldMacNewline = "\r"
func NewFixHandler(fixInfo *metav1.FixInfo) (*FixHandler, error) {
jsonFile, err := os.Open(fixInfo.ReportFile)
if err != nil {
@@ -232,7 +236,9 @@ func (h *FixHandler) getFilePathAndIndex(filePathWithIndex string) (filePath str
}
func (h *FixHandler) ApplyFixToContent(yamlAsString, yamlExpression string) (fixedString string, err error) {
yamlLines := strings.Split(yamlAsString, "\n")
newline := determineNewlineSeparator(yamlAsString)
yamlLines := strings.Split(yamlAsString, newline)
originalRootNodes, err := decodeDocumentRoots(yamlAsString)
@@ -248,9 +254,9 @@ func (h *FixHandler) ApplyFixToContent(yamlAsString, yamlExpression string) (fix
fileFixInfo := getFixInfo(originalRootNodes, fixedRootNodes)
fixedYamlLines := getFixedYamlLines(yamlLines, fileFixInfo)
fixedYamlLines := getFixedYamlLines(yamlLines, fileFixInfo, newline)
fixedString = getStringFromSlice(fixedYamlLines)
fixedString = getStringFromSlice(fixedYamlLines, newline)
return fixedString, nil
}
@@ -344,3 +350,12 @@ func writeFixesToFile(filepath, content string) error {
return nil
}
func determineNewlineSeparator(contents string) string {
switch {
case strings.Contains(contents, windowsNewline):
return windowsNewline
default:
return unixNewline
}
}

View File

@@ -98,6 +98,11 @@ func getTestCases() []indentationTestCase {
`select(di==0).spec.containers += {"name": "redis", "image": "redis"}`,
"inserts/tc-10-01-expected.yaml",
},
{
"inserts/tc-11-00-input-list-insert-new-mapping-crlf-newlines.yaml",
`select(di==0).spec.containers += {"name": "redis", "image": "redis"}`,
"inserts/tc-11-01-expected.yaml",
},
// Removal Scenarios
{

View File

@@ -0,0 +1,11 @@
# Fix to Apply:
# select(di==0).spec.containers += {"name": "redis", "image": "redis"}
apiVersion: v1
kind: Pod
metadata:
name: indented-list-insert-new-object
spec:
containers:
- name: nginx_container
image: nginx

View File

@@ -0,0 +1,13 @@
# Fix to Apply:
# select(di==0).spec.containers += {"name": "redis", "image": "redis"}
apiVersion: v1
kind: Pod
metadata:
name: indented-list-insert-new-object
spec:
containers:
- name: nginx_container
image: nginx
- name: redis
image: redis

View File

@@ -244,7 +244,7 @@ func removeNewLinesAtTheEnd(yamlLines []string) []string {
return yamlLines
}
func getFixedYamlLines(yamlLines []string, fileFixInfo fileFixInfo) (fixedYamlLines []string) {
func getFixedYamlLines(yamlLines []string, fileFixInfo fileFixInfo, newline string) (fixedYamlLines []string) {
// Determining last line requires original yaml lines slice. The placeholder for last line is replaced with the real last line
assignLastLine(fileFixInfo.contentsToAdd, fileFixInfo.linesToRemove, &yamlLines)
@@ -267,7 +267,7 @@ func getFixedYamlLines(yamlLines []string, fileFixInfo fileFixInfo) (fixedYamlLi
lineIdx += 1
}
content := (*fileFixInfo.contentsToAdd)[lineToAddIdx].content
content := (*fileFixInfo.contentsToAdd)[lineToAddIdx].Content(newline)
fixedYamlLines = append(fixedYamlLines, content)
lineToAddIdx += 1

View File

@@ -401,6 +401,6 @@ func updateTracker(nodeList *[]nodeInfo, tracker int) int {
return updatedTracker
}
func getStringFromSlice(yamlLines []string) (fixedYamlString string) {
return strings.Join(yamlLines, "\n")
func getStringFromSlice(yamlLines []string, newline string) (fixedYamlString string) {
return strings.Join(yamlLines, newline)
}