diff --git a/internal/resolution/pm/maven/job.go b/internal/resolution/pm/maven/job.go index 74d0a636..9e42bebb 100644 --- a/internal/resolution/pm/maven/job.go +++ b/internal/resolution/pm/maven/job.go @@ -2,13 +2,19 @@ package maven import ( "path/filepath" + "regexp" + "strings" "github.com/debricked/cli/internal/resolution/job" "github.com/debricked/cli/internal/resolution/pm/util" ) const ( - lockFileExtension = "maven.debricked.lock" + lockFileExtension = "maven.debricked.lock" + nonParseablePomErrRegex = "Non-parseable POM (.*)" + networkUnreachableErrRegex = "Failed to retrieve plugin descriptor" + invalidVersionErrRegex = "('[\\w\\.]+' for [\\w\\.:-]+ must not contain any of these characters .* but found .)" + dependenciesResolveErrRegex = `Could not resolve dependencies for project\s+([\w\.-]+:[\w\.-]+:[\w\.-]+:[\w\.-]+)` ) type Job struct { @@ -27,18 +33,135 @@ func (j *Job) Run() { workingDirectory := filepath.Dir(filepath.Clean(j.GetFile())) cmd, err := j.cmdFactory.MakeDependencyTreeCmd(workingDirectory) if err != nil { - j.Errors().Critical(util.NewPMJobError(err.Error())) + j.handleError(util.NewPMJobError(err.Error())) return } - j.SendStatus("creating dependency graph") + + status := "creating dependency graph" + j.SendStatus(status) var output []byte output, err = cmd.Output() if err != nil { - if output == nil { - j.Errors().Critical(util.NewPMJobError(err.Error())) - } else { - j.Errors().Critical(util.NewPMJobError(string(output))) + errContent := err.Error() + if output != nil { + errContent = string(output) } + + cmdErr := util.NewPMJobError(errContent) + cmdErr.SetStatus(status) + + j.handleError(cmdErr) + } +} + +func (j *Job) handleError(cmdErr job.IError) { + expressions := []string{ + nonParseablePomErrRegex, + networkUnreachableErrRegex, + invalidVersionErrRegex, + dependenciesResolveErrRegex, + } + + for _, expression := range expressions { + regex := regexp.MustCompile(expression) + + if regex.MatchString(cmdErr.Error()) { + cmdErr = j.addDocumentation(expression, regex, cmdErr) + j.Errors().Critical(cmdErr) + + return + } + } + + j.Errors().Critical(cmdErr) +} + +func (j *Job) addDocumentation(expr string, regex *regexp.Regexp, cmdErr job.IError) job.IError { + switch { + case expr == nonParseablePomErrRegex: + cmdErr = j.addNonParseablePomErrorDocumentation(regex, cmdErr) + case expr == networkUnreachableErrRegex: + cmdErr = j.addNetworkUnreachableErrorDocumentation(cmdErr) + case expr == invalidVersionErrRegex: + cmdErr = j.addInvalidVersionErrorDocumentation(regex, cmdErr) + case expr == dependenciesResolveErrRegex: + cmdErr = j.addDependenciesResolveErrorDocumentation(regex, cmdErr) + } + + return cmdErr +} + +func (j *Job) addNonParseablePomErrorDocumentation(regex *regexp.Regexp, cmdErr job.IError) job.IError { + matches := regex.FindAllStringSubmatch(cmdErr.Error(), 1) + message := "the POM file for errors" + if len(matches) > 0 && len(matches[0]) > 1 { + message = matches[0][1] } + + cmdErr.SetDocumentation( + strings.Join( + []string{ + "Failed to build Maven dependency tree.", + "Your POM file is not valid.", + "Please check", + message, + }, " "), + ) + + return cmdErr +} + +func (j *Job) addNetworkUnreachableErrorDocumentation(cmdErr job.IError) job.IError { + cmdErr.SetDocumentation( + strings.Join( + []string{ + "We weren't able to retrieve one or more plugin descriptor(s).", + "Please check your Internet connection and try again.", + }, " "), + ) + + return cmdErr +} + +func (j *Job) addInvalidVersionErrorDocumentation(regex *regexp.Regexp, cmdErr job.IError) job.IError { + matches := regex.FindAllStringSubmatch(cmdErr.Error(), 1) + message := "" + if len(matches) > 0 && len(matches[0]) > 1 { + message = matches[0][1] + } + + cmdErr.SetDocumentation( + strings.Join( + []string{ + "There is an error in dependencies:", + message, + }, " "), + ) + + return cmdErr +} + +func (j *Job) addDependenciesResolveErrorDocumentation(regex *regexp.Regexp, cmdErr job.IError) job.IError { + matches := regex.FindAllStringSubmatch(cmdErr.Error(), 1) + message := "An error occurred during dependencies resolve " + if len(matches) > 0 && len(matches[0]) > 1 { + message += strings.Join( + []string{ + "for: ", + matches[0][1], + "", + }, "") + } + + cmdErr.SetDocumentation( + strings.Join( + []string{ + message, + "\nTry to run `mvn dependency:tree -e` to get more details.\n", + util.InstallPrivateDependencyMessage, + }, ""), + ) + + return cmdErr } diff --git a/internal/resolution/pm/maven/job_test.go b/internal/resolution/pm/maven/job_test.go index 1d5f7db1..4476d146 100644 --- a/internal/resolution/pm/maven/job_test.go +++ b/internal/resolution/pm/maven/job_test.go @@ -17,15 +17,48 @@ func TestNewJob(t *testing.T) { } func TestRunCmdErr(t *testing.T) { - cmdErr := errors.New("cmd-error") - j := NewJob("file", testdata.CmdFactoryMock{Err: cmdErr}) - - go jobTestdata.WaitStatus(j) - - j.Run() - - assert.Len(t, j.Errors().GetAll(), 1) - assert.Contains(t, j.Errors().GetAll(), util.NewPMJobError(cmdErr.Error())) + cases := []struct { + error string + doc string + }{ + { + error: "cmd-error", + doc: util.UnknownError, + }, + { + error: " |[FATAL] Non-parseable POM /home/asus/Projects/playground/maven-project/pom.xml: end tag name must be the same as start tag from line 37 (position: TEXT seen ...1.6... @37:31) @ /home/asus/Projects/playground/maven-project/pom.xml, line 37, column 31\n", + doc: "Failed to build Maven dependency tree. Your POM file is not valid. Please check /home/asus/Projects/playground/maven-project/pom.xml: end tag name must be the same as start tag from line 37 (position: TEXT seen ...1.6... @37:31) @ /home/asus/Projects/playground/maven-project/pom.xml, line 37, column 31", + }, + { + error: " |[WARNING] Failed to retrieve plugin descriptor for org.apache.maven.plugins:maven-compiler-plugin:2.3.2: Plugin org.apache.maven.plugins:maven-compiler-plugin:2.3.2 or one of its dependencies could not be resolved: Failed to read artifact descriptor for org.apache.maven.plugins:maven-compiler-plugin:jar:2.3.2\n", + doc: "We weren't able to retrieve one or more plugin descriptor(s). Please check your Internet connection and try again.", + }, + { + error: " |[ERROR] 'dependencies.dependency.version' for org.hamcrest:hamcrest-library:jar must not contain any of these characters \\/:\"<>|?* but found * @ com.example.maven-project:maven-project:1.0-SNAPSHOT, /home/asus/Projects/playground/maven-project/pom.xml, line 196, column 18\n", + doc: "There is an error in dependencies: 'dependencies.dependency.version' for org.hamcrest:hamcrest-library:jar must not contain any of these characters \\/:\"<>|?* but found *", + }, + { + error: " |[ERROR] Failed to execute goal on project jackpot: Could not resolve dependencies for project com.jeteo:jackpot:war:1.0-SNAPSHOT: The following artifacts could not be resolved: javax.servlet:com.springsource.javax.servlet:jar:2.5.0, javax.servlet:com.springsource.javax.servlet.jsp.jstl:jar:1.2.0 (http://repository.springsource.com/maven/bundles/release) -> [Help 1]\n", + doc: "An error occurred during dependencies resolve for: com.jeteo:jackpot:war:1.0-SNAPSHOT\nTry to run `mvn dependency:tree -e` to get more details.\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.", + }, + } + + for _, c := range cases { + expectedError := util.NewPMJobError(c.error) + expectedError.SetDocumentation(c.doc) + + cmdErr := errors.New(c.error) + j := NewJob("file", testdata.CmdFactoryMock{Err: cmdErr}) + + go jobTestdata.WaitStatus(j) + + j.Run() + + errors := j.Errors().GetAll() + + assert.Len(t, errors, 1) + assert.Contains(t, errors, expectedError) + } } func TestRunCmdOutputErr(t *testing.T) {