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

[Code Change] AzCopy Error Improvements #2647

Merged
merged 12 commits into from
May 16, 2024
2 changes: 1 addition & 1 deletion cmd/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2027,7 +2027,7 @@ func init() {
cooked.commandString = copyHandlerUtil{}.ConstructCommandStringFromArgs()
err = cooked.process()
if err != nil {
glcm.Error("failed to perform copy command due to error: " + err.Error())
glcm.Error("failed to perform copy command due to error: " + err.Error() + getErrorCodeUrl(err))
}

if cooked.dryrunMode {
Expand Down
2 changes: 1 addition & 1 deletion cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func init() {
if err == nil {
glcm.Exit(nil, common.EExitCode.Success())
} else {
glcm.Error(err.Error())
glcm.Error(err.Error() + getErrorCodeUrl(err))
}
},
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var lgCmd = &cobra.Command{
// the errors from adal contains \r\n in the body, get rid of them to make the error easier to look at
prettyErr := strings.Replace(err.Error(), `\r\n`, "\n", -1)
prettyErr += "\n\nNOTE: If your credential was created in the last 5 minutes, please wait a few minutes and try again."
glcm.Error("Failed to perform login command: \n" + prettyErr)
glcm.Error("Failed to perform login command: \n" + prettyErr + getErrorCodeUrl(err))
}
return nil
},
Expand Down
2 changes: 1 addition & 1 deletion cmd/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func init() {
cooked.commandString = copyHandlerUtil{}.ConstructCommandStringFromArgs()
err = cooked.process()
if err != nil {
glcm.Error("failed to perform remove command due to error: " + err.Error())
glcm.Error("failed to perform remove command due to error: " + err.Error() + getErrorCodeUrl(err))
}

if cooked.dryrunMode {
Expand Down
42 changes: 42 additions & 0 deletions cmd/responseErrorParser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cmd

import (
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"strings"
)

// errorURLs - map of error codes that currently have shorthand URLs
var errorURLs = map[bloberror.Code]string{
bloberror.InvalidOperation: "https://aka.ms/AzCopyError/InvalidOperation",
bloberror.MissingRequiredQueryParameter: "https://aka.ms/AzCopyError/MissingRequiredQueryParameter",
bloberror.InvalidHeaderValue: "https://aka.ms/AzCopyError/InvalidHeaderValue",
bloberror.InvalidAuthenticationInfo: "https://aka.ms/AzCopyError/InvalidAuthenticationInfo",
bloberror.NoAuthenticationInformation: "https://aka.ms/AzCopyError/NoAuthenticationInformation",
bloberror.AuthenticationFailed: "https://aka.ms/AzCopyError/AuthenticationFailed",
bloberror.AccountIsDisabled: "https://aka.ms/AzCopyError/AccountIsDisabled",
bloberror.ResourceNotFound: "https://aka.ms/AzCopyError/ResourceNotFound",
bloberror.ResourceTypeMismatch: "https://aka.ms/AzCopyError/ResourceTypeMismatch",
bloberror.CannotVerifyCopySource: "https://aka.ms/AzCopyError/CannotVerifyCopySource",
bloberror.ServerBusy: "https://aka.ms/AzCopyError/ServerBusy",
}

// getErrorCodeUrl - returns url string for specific error codes
func getErrorCodeUrl(err error) string {
var urls []string
for code, url := range errorURLs {
if hasCode(err, code) {
urls = append(urls, url)
}
}

if len(urls) > 0 {
return "ERROR DETAILS: " + strings.Join(urls, "; ")
}

return "" // We do not currently have a URL for this specific error code
}

// hasCode - checks if err contains blob error code
func hasCode(err error, code bloberror.Code) bool {
return strings.Contains(err.Error(), string(code))
}
4 changes: 2 additions & 2 deletions cmd/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -753,13 +753,13 @@ func init() {

cooked, err := raw.cook()
if err != nil {
glcm.Error("error parsing the input given by the user. Failed with error " + err.Error())
glcm.Error("error parsing the input given by the user. Failed with error " + err.Error() + getErrorCodeUrl(err))
}

cooked.commandString = copyHandlerUtil{}.ConstructCommandStringFromArgs()
err = cooked.process()
if err != nil {
glcm.Error("Cannot perform sync due to error: " + err.Error())
glcm.Error("Cannot perform sync due to error: " + err.Error() + getErrorCodeUrl(err))
}
if cooked.dryrunMode {
glcm.Exit(nil, common.EExitCode.Success())
Expand Down
23 changes: 23 additions & 0 deletions e2etest/newe2e_task_resourcemanagement.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,26 @@ func ValidateErrorOutput(a Asserter, stdout AzCopyStdout, errorMsg string) {
fmt.Println(stdout.String())
a.Error("expected error message not found in azcopy output")
}

func ValidateMultipleErrors(a Asserter, stdout AzCopyStdout, errorMsg []string) {
if dryrunner, ok := a.(DryrunAsserter); ok && dryrunner.Dryrun() {
return
}
for _, line := range stdout.RawStdout() {
if checkMultipleErrors(errorMsg, line) {
return
}
}
fmt.Println(stdout.String())
a.Error("expected error message not found in azcopy output")
}

func checkMultipleErrors(errorMsg []string, line string) bool {
for _, e := range errorMsg {
if strings.Contains(line, e) {
return true
}
}

return false
}
34 changes: 34 additions & 0 deletions e2etest/zt_newe2e_basic_functionality_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,37 @@ func (s *BasicFunctionalitySuite) Scenario_SingleFileUploadDownload_EmptySAS(svm
// Validate that the stdout contains the missing sas message
ValidateErrorOutput(svm, stdout, "Please authenticate using Microsoft Entra ID (https://aka.ms/AzCopy/AuthZ), use AzCopy login, or append a SAS token to your Azure URL.")
}

func (s *BasicFunctionalitySuite) Scenario_SingleFileUploadDownload_EmptySASErrorCodes(svm *ScenarioVariationManager) {
azCopyVerb := ResolveVariation(svm, []AzCopyVerb{AzCopyVerbCopy, AzCopyVerbSync})

dstObj := CreateResource[ContainerResourceManager](svm, GetRootResource(svm, ResolveVariation(svm, []common.Location{common.ELocation.Local(), common.ELocation.Blob(), common.ELocation.File(), common.ELocation.BlobFS()})), ResourceDefinitionContainer{}).GetObject(svm, "test", common.EEntityType.File())

// Scale up from service to object
srcObj := CreateResource[ObjectResourceManager](svm, GetRootResource(svm, ResolveVariation(svm, []common.Location{common.ELocation.Local(), common.ELocation.Blob(), common.ELocation.File(), common.ELocation.BlobFS()})), ResourceDefinitionObject{})

// no local <-> local
if srcObj.Location().IsLocal() == dstObj.Location().IsLocal() {
svm.InvalidateScenario()
return
}

stdout, _ := RunAzCopy(
svm,
AzCopyCommand{
Verb: azCopyVerb,
Targets: []ResourceManager{
AzCopyTarget{srcObj, EExplicitCredentialType.PublicAuth(), CreateAzCopyTargetOptions{}},
AzCopyTarget{dstObj, EExplicitCredentialType.PublicAuth(), CreateAzCopyTargetOptions{}},
},
Flags: CopyFlags{
CopySyncCommonFlags: CopySyncCommonFlags{
Recursive: pointerTo(true),
},
},
ShouldFail: true,
})

// Validate that the stdout contains these error URLs
ValidateMultipleErrors(svm, stdout, []string{"https://aka.ms/AzCopyError/NoAuthenticationInformation", "https://aka.ms/AzCopyError/ResourceNotFound"})
}
Loading