Skip to content

Commit d9d6d7e

Browse files
Merge pull request #2141 from step-security/fix-dependabot-issues
Fix Dependabot config indentation issue
2 parents 1000b48 + 295bd88 commit d9d6d7e

File tree

7 files changed

+140
-25
lines changed

7 files changed

+140
-25
lines changed

remediation/dependabot/dependabotconfig.go

Lines changed: 95 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package dependabot
33
import (
44
"bufio"
55
"encoding/json"
6+
"errors"
7+
"fmt"
68
"strings"
79

810
dependabot "github.com/paulvollmer/dependabot-config-go"
9-
"gopkg.in/yaml.v2"
11+
"gopkg.in/yaml.v3"
1012
)
1113

1214
type UpdateDependabotConfigResponse struct {
@@ -27,29 +29,79 @@ type UpdateDependabotConfigRequest struct {
2729
Content string
2830
}
2931

32+
// getIndentation returns the indentation level of the first list found in a given YAML string.
33+
// If the YAML string is empty or invalid, or if no list is found, it returns an error.
34+
func getIndentation(dependabotConfig string) (int, error) {
35+
// Initialize an empty YAML node
36+
t := yaml.Node{}
37+
38+
// Unmarshal the YAML string into the node
39+
err := yaml.Unmarshal([]byte(dependabotConfig), &t)
40+
if err != nil {
41+
return 0, fmt.Errorf("unable to parse yaml: %w", err)
42+
}
43+
44+
// Retrieve the top node of the YAML document
45+
topNode := t.Content
46+
if len(topNode) == 0 {
47+
return 0, errors.New("file provided is empty or invalid")
48+
}
49+
50+
// Check for the first list and its indentation level
51+
for _, n := range topNode[0].Content {
52+
if n.Value == "" && n.Tag == "!!seq" {
53+
// Return the column of the first list found
54+
return n.Column, nil
55+
}
56+
}
57+
58+
// Return an error if no list was found
59+
return 0, errors.New("no list found in yaml")
60+
}
61+
62+
// UpdateDependabotConfig is used to update dependabot configuration and returns an UpdateDependabotConfigResponse.
3063
func UpdateDependabotConfig(dependabotConfig string) (*UpdateDependabotConfigResponse, error) {
3164
var updateDependabotConfigRequest UpdateDependabotConfigRequest
32-
json.Unmarshal([]byte(dependabotConfig), &updateDependabotConfigRequest)
65+
66+
// Handle error in json unmarshalling
67+
err := json.Unmarshal([]byte(dependabotConfig), &updateDependabotConfigRequest)
68+
if err != nil {
69+
return nil, fmt.Errorf("failed to unmarshal JSON from dependabotConfig: %v", err)
70+
}
71+
3372
inputConfigFile := []byte(updateDependabotConfigRequest.Content)
3473
configMetadata := dependabot.New()
35-
err := configMetadata.Unmarshal(inputConfigFile)
74+
err = configMetadata.Unmarshal(inputConfigFile)
3675
if err != nil {
37-
return nil, err
76+
return nil, fmt.Errorf("failed to unmarshal dependabot config: %v", err)
3877
}
3978

79+
indentation := 3
80+
4081
response := new(UpdateDependabotConfigResponse)
4182
response.FinalOutput = updateDependabotConfigRequest.Content
4283
response.OriginalInput = updateDependabotConfigRequest.Content
4384
response.IsChanged = false
4485

86+
// Using strings.Builder for efficient string concatenation
87+
var finalOutput strings.Builder
88+
finalOutput.WriteString(response.FinalOutput)
89+
4590
if updateDependabotConfigRequest.Content == "" {
4691
if len(updateDependabotConfigRequest.Ecosystems) == 0 {
4792
return response, nil
4893
}
49-
response.FinalOutput = "version: 2\nupdates:"
94+
finalOutput.WriteString("version: 2\nupdates:")
5095
} else {
51-
response.FinalOutput += "\n"
96+
if !strings.HasSuffix(response.FinalOutput, "\n") {
97+
finalOutput.WriteString("\n")
98+
}
99+
indentation, err = getIndentation(string(inputConfigFile))
100+
if err != nil {
101+
return nil, fmt.Errorf("failed to get indentation: %v", err)
102+
}
52103
}
104+
53105
for _, Update := range updateDependabotConfigRequest.Ecosystems {
54106
updateAlreadyExist := false
55107
for _, update := range configMetadata.Updates {
@@ -58,37 +110,56 @@ func UpdateDependabotConfig(dependabotConfig string) (*UpdateDependabotConfigRes
58110
break
59111
}
60112
}
61-
if !updateAlreadyExist {
62-
item := dependabot.Update{}
63-
item.PackageEcosystem = Update.PackageEcosystem
64-
item.Directory = Update.Directory
65113

66-
schedule := dependabot.Schedule{}
67-
schedule.Interval = Update.Interval
68-
69-
item.Schedule = schedule
70-
items := []dependabot.Update{}
71-
items = append(items, item)
114+
if !updateAlreadyExist {
115+
item := dependabot.Update{
116+
PackageEcosystem: Update.PackageEcosystem,
117+
Directory: Update.Directory,
118+
Schedule: dependabot.Schedule{Interval: Update.Interval},
119+
}
120+
items := []dependabot.Update{item}
72121
addedItem, err := yaml.Marshal(items)
73-
data := string(addedItem)
122+
if err != nil {
123+
return nil, fmt.Errorf("failed to marshal update items: %v", err)
124+
}
74125

75-
data = addIndentation(data)
126+
data, err := addIndentation(string(addedItem), indentation)
76127
if err != nil {
77-
return nil, err
128+
return nil, fmt.Errorf("failed to add indentation: %v", err)
78129
}
79-
response.FinalOutput = response.FinalOutput + data
130+
finalOutput.WriteString(data)
80131
response.IsChanged = true
81132
}
82133
}
83134

135+
// Set FinalOutput to the built string
136+
response.FinalOutput = finalOutput.String()
137+
84138
return response, nil
85139
}
86140

87-
func addIndentation(data string) string {
141+
// addIndentation adds a certain number of spaces to the start of each line in the input string.
142+
// It returns a new string with the added indentation.
143+
func addIndentation(data string, indentation int) (string, error) {
88144
scanner := bufio.NewScanner(strings.NewReader(data))
89-
finalData := "\n"
145+
var finalData strings.Builder
146+
147+
// Create the indentation string
148+
spaces := strings.Repeat(" ", indentation-1)
149+
150+
finalData.WriteString("\n")
151+
152+
// Add indentation to each line
90153
for scanner.Scan() {
91-
finalData += " " + scanner.Text() + "\n"
154+
finalData.WriteString(spaces)
155+
finalData.WriteString(scanner.Text())
156+
finalData.WriteString("\n")
157+
}
158+
159+
// Check for scanning errors
160+
if err := scanner.Err(); err != nil {
161+
return "", fmt.Errorf("error during scanning: %w", err)
92162
}
93-
return finalData
163+
164+
return finalData.String(), nil
94165
}

remediation/dependabot/dependabotconfig_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ func TestConfigDependabotFile(t *testing.T) {
3838
Ecosystems: []Ecosystem{{"github-actions", "/", "daily"}, {"npm", "/sample", "daily"}},
3939
isChanged: true,
4040
},
41+
{
42+
fileName: "No-Indentation.yml",
43+
Ecosystems: []Ecosystem{{"npm", "/sample", "daily"}},
44+
isChanged: true,
45+
},
46+
{
47+
fileName: "High-Indentation.yml",
48+
Ecosystems: []Ecosystem{{"npm", "/sample", "daily"}},
49+
isChanged: true,
50+
},
4151
}
4252

4353
for _, test := range tests {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: github-actions
4+
directory: "/"
5+
schedule:
6+
interval: daily
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: github-actions
4+
directory: "/"
5+
schedule:
6+
interval: daily

testfiles/dependabotfiles/input/Same-ecosystem-different-directory.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ updates:
44
# Files stored in `app` directory
55
directory: "/app"
66
schedule:
7-
interval: "daily"
7+
interval: "daily"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: github-actions
4+
directory: "/"
5+
schedule:
6+
interval: daily
7+
8+
- package-ecosystem: npm
9+
directory: /sample
10+
schedule:
11+
interval: daily
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: github-actions
4+
directory: "/"
5+
schedule:
6+
interval: daily
7+
8+
- package-ecosystem: npm
9+
directory: /sample
10+
schedule:
11+
interval: daily

0 commit comments

Comments
 (0)