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

Enhance NuGet errors #175

Merged
merged 2 commits into from
Jan 2, 2024
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
135 changes: 125 additions & 10 deletions internal/resolution/pm/nuget/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ package nuget
import (
"fmt"
"os"
"regexp"
"strings"

"github.com/debricked/cli/internal/resolution/job"
"github.com/debricked/cli/internal/resolution/pm/util"
)

const (
nuget = "dotnet"
nuget = "dotnet"
versionNotFoundErrRegex = `Unable to find [\w\s]*package ('[^"'\s:]+' with version [^"'\n:]+)`
dependencyNotFoundErrRegex = `'([^"'\s:]+)'. No packages exist`
unableToResolveErrRegex = `Unable to resolve '([^"'\n:]+)'`
noInternetErrRegex = `Unable to load the service index for source ([^"'\s]+).`
)

type Job struct {
Expand Down Expand Up @@ -37,13 +43,13 @@ func (j *Job) Install() bool {

func (j *Job) Run() {
if j.install {

j.SendStatus("installing dependencies")
output, err := j.runInstallCmd()
status := "installing dependencies"
j.SendStatus(status)
output, cmd, err := j.runInstallCmd()
defer j.cleanupTempCsproj()
if err != nil {
formatted_error := fmt.Errorf("%s\n%s", output, err)
j.Errors().Critical(util.NewPMJobError(formatted_error.Error()))
j.handleError(j.createError(formatted_error.Error(), cmd, status))

return
}
Expand All @@ -53,20 +59,20 @@ func (j *Job) Run() {

var osRemoveAll = os.RemoveAll

func (j *Job) runInstallCmd() ([]byte, error) {
func (j *Job) runInstallCmd() ([]byte, string, error) {

j.nugetCommand = nuget
installCmd, err := j.cmdFactory.MakeInstallCmd(j.nugetCommand, j.GetFile())
if err != nil {
return nil, err
return nil, installCmd.String(), err
}

installCmdOutput, err := installCmd.Output()
if err != nil {
return installCmdOutput, j.GetExitError(err, "")
return installCmdOutput, installCmd.String(), j.GetExitError(err, "")
}

return installCmdOutput, nil
return installCmdOutput, installCmd.String(), nil
}

func (j *Job) cleanupTempCsproj() {
Expand All @@ -77,7 +83,116 @@ func (j *Job) cleanupTempCsproj() {
err := osRemoveAll(tempFile)
formatted_error := fmt.Errorf("failed to remove temporary .csproj file: %s", err)
if err != nil {
j.Errors().Critical(util.NewPMJobError(formatted_error.Error()))
j.handleError(j.createError(formatted_error.Error(), "", "cleanup"))
}
}
}

func (j *Job) createError(error string, cmd string, status string) job.IError {
cmdError := util.NewPMJobError(error)
cmdError.SetCommand(cmd)
cmdError.SetStatus(status)

return cmdError
}

func (j *Job) handleError(cmdError job.IError) {
expressions := []string{
versionNotFoundErrRegex,
dependencyNotFoundErrRegex,
unableToResolveErrRegex,
noInternetErrRegex,
}

for _, expression := range expressions {
regex := regexp.MustCompile(expression)
matches := regex.FindAllStringSubmatch(cmdError.Error(), -1)

if len(matches) > 0 {
cmdError = j.addDocumentation(expression, matches, cmdError)
j.Errors().Append(cmdError)

return
}
}

j.Errors().Append(cmdError)
}

func (j *Job) addDocumentation(expr string, matches [][]string, cmdError job.IError) job.IError {
documentation := cmdError.Documentation()

switch expr {
case versionNotFoundErrRegex:
documentation = getVersionNotFoundErrorDocumentation(matches)
case dependencyNotFoundErrRegex:
documentation = getDependencyNotFoundErrorDocumentation(matches)
case unableToResolveErrRegex:
documentation = getUnableToResolveErrorDocumentation(matches)
case noInternetErrRegex:
documentation = getNoInternetErrorDocumentation(matches)
}

cmdError.SetDocumentation(documentation)

return cmdError
}

func getVersionNotFoundErrorDocumentation(matches [][]string) string {
dependency := ""
if len(matches) > 0 && len(matches[0]) > 1 {
dependency = matches[0][1]
}

return strings.Join(
[]string{
"Failed to find package",
dependency + ".",
"Please check that package versions are correct in the manifest file.",
}, " ")
}

func getDependencyNotFoundErrorDocumentation(matches [][]string) string {
dependency := ""
if len(matches) > 0 && len(matches[0]) > 1 {
dependency = matches[0][1]
}

return strings.Join(
[]string{
"Failed to find package",
"\"" + dependency + "\"",
"that satisfies the requirements.",
"Please check that dependencies are correct in the manifest file.",
"\n" + util.InstallPrivateDependencyMessage,
}, " ")
}

func getUnableToResolveErrorDocumentation(matches [][]string) string {
dependency := ""
if len(matches) > 0 && len(matches[0]) > 1 {
dependency = matches[0][1]
}

return strings.Join(
[]string{
"Couldn't resolve",
"\"" + dependency + "\".",
"Please check if it exists and NuGet sources are configured properly.",
}, " ")
}

func getNoInternetErrorDocumentation(matches [][]string) string {
registry := ""
if len(matches) > 0 && len(matches[0]) > 1 {
registry = matches[0][1]
}

return strings.Join(
[]string{
"Registry",
"\"" + registry + "\"",
"is not available at the moment.",
"There might be a trouble with your network connection.",
}, " ")
}
70 changes: 59 additions & 11 deletions internal/resolution/pm/nuget/job_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestRunInstall(t *testing.T) {
cmdFactoryMock := testdata.NewEchoCmdFactory()
j := NewJob("file", false, cmdFactoryMock)

_, err := j.runInstallCmd()
_, _, err := j.runInstallCmd()
assert.NoError(t, err)

assert.False(t, j.Errors().HasError())
Expand All @@ -37,7 +37,7 @@ func TestRunInstallPackagesConfig(t *testing.T) {
cmdFactoryMock.GetTempoCsprojReturn = "tempo.csproj"
j := NewJob("packages.config", false, cmdFactoryMock)

_, err := j.runInstallCmd()
_, _, err := j.runInstallCmd()
assert.NoError(t, err)

assert.False(t, j.Errors().HasError())
Expand All @@ -60,10 +60,13 @@ func TestRunInstallPackagesConfigRemoveAllErr(t *testing.T) {
cmdFactoryMock.GetTempoCsprojReturn = "tempo.csproj"
j := NewJob("packages.config", true, cmdFactoryMock)

expectedError := util.NewPMJobError(cmdErrGt.Error())
expectedError.SetStatus("cleanup")

go jobTestdata.WaitStatus(j)
j.Run()
errors := j.Errors().GetAll()
assert.Equal(t, errors[0], util.NewPMJobError(cmdErrGt.Error()))
assert.Equal(t, errors[0], expectedError)

}

Expand All @@ -76,16 +79,61 @@ func TestInstall(t *testing.T) {
}

func TestRunInstallCmdErr(t *testing.T) {
cmdErr := errors.New("cmd-error")
cmdErrGt := errors.New("\ncmd-error")
cmdFactoryMock := testdata.NewEchoCmdFactory()
cmdFactoryMock.MakeInstallErr = cmdErr
j := NewJob("file", true, cmdFactoryMock)
cases := []struct {
name string
error string
doc string
}{
{
name: "General error",
error: "cmd-error",
doc: util.UnknownError,
},
{
name: "Invalid package version",
error: "Unable to find a stable package 'PackageId' with version (>= 3.0.0)\n - Found 10 version(s) in 'sourceA' [ Nearest version: '4.0.0-rc-2129' ]\n - Found 9 version(s) in 'sourceB' [ Nearest version: '3.0.0-beta-00032' ]\n - Found 0 version(s) in 'sourceC'\n - Found 0 version(s) in 'sourceD'",
doc: "Failed to find package 'PackageId' with version (>= 3.0.0). Please check that package versions are correct in the manifest file.",
},
{
name: "Invalid package name",
error: "Unable to find package 'PackageId'. No packages exist with this id in source(s): sourceA, sourceB, sourceC",
doc: "Failed to find package \"PackageId\" that satisfies the requirements. Please check that dependencies are correct in the manifest file. \nIf this is a private dependency, please make sure that the debricked CLI has access to install it or pre-install it before running the debricked CLI.",
},
{
name: "Unable to resolve package",
error: "Unable to resolve 'Dependency (>= 1.0.0)' for 'TargetFramework'",
doc: "Couldn't resolve \"Dependency (>= 1.0.0)\". Please check if it exists and NuGet sources are configured properly.",
},
{
name: "No internet connection",
error: "Unable to load the service index for source https://api.nuget.org/v3/index.json.\nAn error occurred while sending the request. \nThe remote name could not be resolved: 'api.nuget.org'",
doc: "Registry \"https://api.nuget.org/v3/index.json\" is not available at the moment. There might be a trouble with your network connection.",
},
}

go jobTestdata.WaitStatus(j)
j.Run()
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
cmdErr := errors.New(c.error)
cmdFactoryMock := testdata.NewEchoCmdFactory()
cmdFactoryMock.MakeInstallErr = cmdErr
cmd, _ := cmdFactoryMock.MakeInstallCmd("echo", "package.json")

assert.Equal(t, j.Errors().GetAll()[0], util.NewPMJobError(cmdErrGt.Error()))
expectedError := util.NewPMJobError("\n" + c.error)
expectedError.SetDocumentation(c.doc)
expectedError.SetStatus("installing dependencies")
expectedError.SetCommand(cmd.String())

j := NewJob("file", true, cmdFactoryMock)

go jobTestdata.WaitStatus(j)
j.Run()

allErrors := j.Errors().GetAll()

assert.Len(t, j.Errors().GetAll(), 1)
assert.Contains(t, allErrors, expectedError)
})
}
}

func TestRunInstallCmdOutputErr(t *testing.T) {
Expand Down
Loading