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 Dependabot config indentation issue #2141

Merged
merged 1 commit into from
May 18, 2023
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
119 changes: 95 additions & 24 deletions remediation/dependabot/dependabotconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package dependabot
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"strings"

dependabot "github.com/paulvollmer/dependabot-config-go"
"gopkg.in/yaml.v2"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • [Medium]Use the correct YAML library
    The current library being used is from an older version of YAML. The new library is YAML V3, see 'gopkg.in/yaml.v3'. Replace the current import path with 'gopkg.in/yaml.v3' code line:gopkg.in/yaml.v2

"gopkg.in/yaml.v3"
)

type UpdateDependabotConfigResponse struct {
Expand All @@ -27,29 +29,79 @@ type UpdateDependabotConfigRequest struct {
Content string
}

// getIndentation returns the indentation level of the first list found in a given YAML string.
// If the YAML string is empty or invalid, or if no list is found, it returns an error.
func getIndentation(dependabotConfig string) (int, error) {
// Initialize an empty YAML node
t := yaml.Node{}

// Unmarshal the YAML string into the node
err := yaml.Unmarshal([]byte(dependabotConfig), &t)
if err != nil {
return 0, fmt.Errorf("unable to parse yaml: %w", err)
}

// Retrieve the top node of the YAML document
topNode := t.Content
if len(topNode) == 0 {
return 0, errors.New("file provided is empty or invalid")
}

// Check for the first list and its indentation level
for _, n := range topNode[0].Content {
if n.Value == "" && n.Tag == "!!seq" {
// Return the column of the first list found
return n.Column, nil
}
}

// Return an error if no list was found
return 0, errors.New("no list found in yaml")
}

// UpdateDependabotConfig is used to update dependabot configuration and returns an UpdateDependabotConfigResponse.
func UpdateDependabotConfig(dependabotConfig string) (*UpdateDependabotConfigResponse, error) {
var updateDependabotConfigRequest UpdateDependabotConfigRequest
json.Unmarshal([]byte(dependabotConfig), &updateDependabotConfigRequest)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • [High]Handle error in json unmarshalling
    There is a json.Unmarshal operation that can potentially return an error. As such, the error should be handled appropriately instead of returning it directly. Handle the error by logging, returning an error message or by taking some other appropriate action. code line:json.Unmarshal([]byte(dependabotConfig), &updateDependabotConfigRequest)


// Handle error in json unmarshalling
err := json.Unmarshal([]byte(dependabotConfig), &updateDependabotConfigRequest)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON from dependabotConfig: %v", err)
}

inputConfigFile := []byte(updateDependabotConfigRequest.Content)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • [Low]Check if inputConfigFile is empty
    The current code does not check if inputConfigFile is empty or not. Add a condition to check if inputConfigFile is empty or not. code line:inputConfigFile := []byte(updateDependabotConfigRequest.Content)

configMetadata := dependabot.New()
err := configMetadata.Unmarshal(inputConfigFile)
err = configMetadata.Unmarshal(inputConfigFile)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to unmarshal dependabot config: %v", err)
}

indentation := 3

response := new(UpdateDependabotConfigResponse)
response.FinalOutput = updateDependabotConfigRequest.Content
response.OriginalInput = updateDependabotConfigRequest.Content
response.IsChanged = false

// Using strings.Builder for efficient string concatenation
var finalOutput strings.Builder
finalOutput.WriteString(response.FinalOutput)

if updateDependabotConfigRequest.Content == "" {
if len(updateDependabotConfigRequest.Ecosystems) == 0 {
return response, nil
}
response.FinalOutput = "version: 2\nupdates:"
finalOutput.WriteString("version: 2\nupdates:")
} else {
response.FinalOutput += "\n"
if !strings.HasSuffix(response.FinalOutput, "\n") {
finalOutput.WriteString("\n")
}
indentation, err = getIndentation(string(inputConfigFile))
if err != nil {
return nil, fmt.Errorf("failed to get indentation: %v", err)
}
}

for _, Update := range updateDependabotConfigRequest.Ecosystems {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • [Medium]Sort the ecosystem names by alphabetical order
    The ecosystem packages are currently in a random order. Sort the ecosystem names by alphabetical order. code line:for _, Update := range updateDependabotConfigRequest.Ecosystems {

updateAlreadyExist := false
for _, update := range configMetadata.Updates {
Expand All @@ -58,37 +110,56 @@ func UpdateDependabotConfig(dependabotConfig string) (*UpdateDependabotConfigRes
break
}
}
if !updateAlreadyExist {
item := dependabot.Update{}
item.PackageEcosystem = Update.PackageEcosystem
item.Directory = Update.Directory

schedule := dependabot.Schedule{}
schedule.Interval = Update.Interval

item.Schedule = schedule
items := []dependabot.Update{}
items = append(items, item)
if !updateAlreadyExist {
item := dependabot.Update{
PackageEcosystem: Update.PackageEcosystem,
Directory: Update.Directory,
Schedule: dependabot.Schedule{Interval: Update.Interval},
}
items := []dependabot.Update{item}
addedItem, err := yaml.Marshal(items)
data := string(addedItem)
if err != nil {
return nil, fmt.Errorf("failed to marshal update items: %v", err)
}

data = addIndentation(data)
data, err := addIndentation(string(addedItem), indentation)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to add indentation: %v", err)
}
response.FinalOutput = response.FinalOutput + data
finalOutput.WriteString(data)
response.IsChanged = true
}
}

// Set FinalOutput to the built string
response.FinalOutput = finalOutput.String()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • [Low]Check for final newline character
    The final YAML output does not always end with a newline. Add a newline character if the output does not end with one. code line:response.FinalOutput = finalOutput.String()


return response, nil
}

func addIndentation(data string) string {
// addIndentation adds a certain number of spaces to the start of each line in the input string.
// It returns a new string with the added indentation.
func addIndentation(data string, indentation int) (string, error) {
scanner := bufio.NewScanner(strings.NewReader(data))
finalData := "\n"
var finalData strings.Builder

// Create the indentation string
spaces := strings.Repeat(" ", indentation-1)

finalData.WriteString("\n")

// Add indentation to each line
for scanner.Scan() {
finalData += " " + scanner.Text() + "\n"
finalData.WriteString(spaces)
finalData.WriteString(scanner.Text())
finalData.WriteString("\n")
}

// Check for scanning errors
if err := scanner.Err(); err != nil {
return "", fmt.Errorf("error during scanning: %w", err)
}
return finalData

return finalData.String(), nil
}
10 changes: 10 additions & 0 deletions remediation/dependabot/dependabotconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ func TestConfigDependabotFile(t *testing.T) {
Ecosystems: []Ecosystem{{"github-actions", "/", "daily"}, {"npm", "/sample", "daily"}},
isChanged: true,
},
{
fileName: "No-Indentation.yml",
Ecosystems: []Ecosystem{{"npm", "/sample", "daily"}},
isChanged: true,
},
{
fileName: "High-Indentation.yml",
Ecosystems: []Ecosystem{{"npm", "/sample", "daily"}},
isChanged: true,
},
}

for _, test := range tests {
Expand Down
6 changes: 6 additions & 0 deletions testfiles/dependabotfiles/input/High-Indentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
6 changes: 6 additions & 0 deletions testfiles/dependabotfiles/input/No-Indentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • [Low]Add an end of file newline character
    The last line of a file should always end with a newline character. Without a newline character, some text editors may not display the last line properly, or may automatically add one, which can cause confusion or make diffs harder to read. Add a newline to the end of the file code line:+ interval: daily

Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ updates:
# Files stored in `app` directory
directory: "/app"
schedule:
interval: "daily"
interval: "daily"
11 changes: 11 additions & 0 deletions testfiles/dependabotfiles/output/High-Indentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily

- package-ecosystem: npm
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • [Low]Add a package manager lock file
    A lock file helps to prevent version conflicts when installing dependencies. Add a lock file for the package manager. code line:- package-ecosystem: npm

directory: /sample
schedule:
interval: daily
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • [Low]Remove redundant 'interval' key
    'interval' key is already specified in the parent level, hence it is not needed in this child level. Remove the child 'interval' key. code line: interval: daily

11 changes: 11 additions & 0 deletions testfiles/dependabotfiles/output/No-Indentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: github-actions
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • [Medium]Avoid using root directory as the directory to run updates in
    The current configuration is running updates in the root directory which can be dangerous and update unexpected packages. Specify the exact directory you would like to update. code line:- package-ecosystem: github-actions

directory: "/"
schedule:
interval: daily

- package-ecosystem: npm
directory: /sample
schedule:
interval: daily
Loading