Skip to content

Commit 43eb15e

Browse files
[Code Change] AzCopy Error Improvements (#2647)
* initial changes for error codes * support multiple error codes * additional places for error code url * e2e test for error codes * updating tests * update sync test
1 parent a96df7b commit 43eb15e

10 files changed

+178
-6
lines changed

cmd/copy.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2027,7 +2027,7 @@ func init() {
20272027
cooked.commandString = copyHandlerUtil{}.ConstructCommandStringFromArgs()
20282028
err = cooked.process()
20292029
if err != nil {
2030-
glcm.Error("failed to perform copy command due to error: " + err.Error())
2030+
glcm.Error("failed to perform copy command due to error: " + err.Error() + getErrorCodeUrl(err))
20312031
}
20322032

20332033
if cooked.dryrunMode {

cmd/list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ func init() {
187187
if err == nil {
188188
glcm.Exit(nil, common.EExitCode.Success())
189189
} else {
190-
glcm.Error(err.Error())
190+
glcm.Error(err.Error() + getErrorCodeUrl(err))
191191
}
192192
},
193193
}

cmd/login.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ var lgCmd = &cobra.Command{
5555
// the errors from adal contains \r\n in the body, get rid of them to make the error easier to look at
5656
prettyErr := strings.Replace(err.Error(), `\r\n`, "\n", -1)
5757
prettyErr += "\n\nNOTE: If your credential was created in the last 5 minutes, please wait a few minutes and try again."
58-
glcm.Error("Failed to perform login command: \n" + prettyErr)
58+
glcm.Error("Failed to perform login command: \n" + prettyErr + getErrorCodeUrl(err))
5959
}
6060
return nil
6161
},

cmd/remove.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func init() {
8989
cooked.commandString = copyHandlerUtil{}.ConstructCommandStringFromArgs()
9090
err = cooked.process()
9191
if err != nil {
92-
glcm.Error("failed to perform remove command due to error: " + err.Error())
92+
glcm.Error("failed to perform remove command due to error: " + err.Error() + getErrorCodeUrl(err))
9393
}
9494

9595
if cooked.dryrunMode {

cmd/responseErrorParser.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package cmd
2+
3+
import (
4+
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
5+
"strings"
6+
)
7+
8+
// errorURLs - map of error codes that currently have shorthand URLs
9+
var errorURLs = map[bloberror.Code]string{
10+
bloberror.InvalidOperation: "https://aka.ms/AzCopyError/InvalidOperation",
11+
bloberror.MissingRequiredQueryParameter: "https://aka.ms/AzCopyError/MissingRequiredQueryParameter",
12+
bloberror.InvalidHeaderValue: "https://aka.ms/AzCopyError/InvalidHeaderValue",
13+
bloberror.InvalidAuthenticationInfo: "https://aka.ms/AzCopyError/InvalidAuthenticationInfo",
14+
bloberror.NoAuthenticationInformation: "https://aka.ms/AzCopyError/NoAuthenticationInformation",
15+
bloberror.AuthenticationFailed: "https://aka.ms/AzCopyError/AuthenticationFailed",
16+
bloberror.AccountIsDisabled: "https://aka.ms/AzCopyError/AccountIsDisabled",
17+
bloberror.ResourceNotFound: "https://aka.ms/AzCopyError/ResourceNotFound",
18+
bloberror.ResourceTypeMismatch: "https://aka.ms/AzCopyError/ResourceTypeMismatch",
19+
bloberror.CannotVerifyCopySource: "https://aka.ms/AzCopyError/CannotVerifyCopySource",
20+
bloberror.ServerBusy: "https://aka.ms/AzCopyError/ServerBusy",
21+
}
22+
23+
// getErrorCodeUrl - returns url string for specific error codes
24+
func getErrorCodeUrl(err error) string {
25+
var urls []string
26+
for code, url := range errorURLs {
27+
if hasCode(err, code) {
28+
urls = append(urls, url)
29+
}
30+
}
31+
32+
if len(urls) > 0 {
33+
return "ERROR DETAILS: " + strings.Join(urls, "; ")
34+
}
35+
36+
return "" // We do not currently have a URL for this specific error code
37+
}
38+
39+
// hasCode - checks if err contains blob error code
40+
func hasCode(err error, code bloberror.Code) bool {
41+
return strings.Contains(err.Error(), string(code))
42+
}

cmd/sync.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -753,13 +753,13 @@ func init() {
753753

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

759759
cooked.commandString = copyHandlerUtil{}.ConstructCommandStringFromArgs()
760760
err = cooked.process()
761761
if err != nil {
762-
glcm.Error("Cannot perform sync due to error: " + err.Error())
762+
glcm.Error("Cannot perform sync due to error: " + err.Error() + getErrorCodeUrl(err))
763763
}
764764
if cooked.dryrunMode {
765765
glcm.Exit(nil, common.EExitCode.Success())

e2etest/newe2e_task_resourcemanagement.go

+23
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,26 @@ func ValidateErrorOutput(a Asserter, stdout AzCopyStdout, errorMsg string) {
220220
fmt.Println(stdout.String())
221221
a.Error("expected error message not found in azcopy output")
222222
}
223+
224+
func ValidateContainsError(a Asserter, stdout AzCopyStdout, errorMsg []string) {
225+
if dryrunner, ok := a.(DryrunAsserter); ok && dryrunner.Dryrun() {
226+
return
227+
}
228+
for _, line := range stdout.RawStdout() {
229+
if checkMultipleErrors(errorMsg, line) {
230+
return
231+
}
232+
}
233+
fmt.Println(stdout.String())
234+
a.Error("expected error message not found in azcopy output")
235+
}
236+
237+
func checkMultipleErrors(errorMsg []string, line string) bool {
238+
for _, e := range errorMsg {
239+
if strings.Contains(line, e) {
240+
return true
241+
}
242+
}
243+
244+
return false
245+
}

e2etest/zt_newe2e_basic_functionality_test.go

+63
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,66 @@ func (s *BasicFunctionalitySuite) Scenario_SingleFileUploadDownload_EmptySAS(svm
9898
// Validate that the stdout contains the missing sas message
9999
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.")
100100
}
101+
102+
func (s *BasicFunctionalitySuite) Scenario_Sync_EmptySASErrorCodes(svm *ScenarioVariationManager) {
103+
dstObj := CreateResource[ContainerResourceManager](svm, GetRootResource(svm, ResolveVariation(svm, []common.Location{common.ELocation.Blob(), common.ELocation.File(), common.ELocation.BlobFS()})), ResourceDefinitionContainer{}).GetObject(svm, "test", common.EEntityType.File())
104+
105+
// Scale up from service to object
106+
srcObj := CreateResource[ObjectResourceManager](svm, GetRootResource(svm, ResolveVariation(svm, []common.Location{common.ELocation.Local(), common.ELocation.Blob(), common.ELocation.File(), common.ELocation.BlobFS()})), ResourceDefinitionObject{})
107+
108+
// no local <-> local
109+
if srcObj.Location().IsLocal() == dstObj.Location().IsLocal() {
110+
svm.InvalidateScenario()
111+
return
112+
}
113+
114+
stdout, _ := RunAzCopy(
115+
svm,
116+
AzCopyCommand{
117+
Verb: AzCopyVerbSync,
118+
Targets: []ResourceManager{
119+
AzCopyTarget{srcObj, EExplicitCredentialType.PublicAuth(), CreateAzCopyTargetOptions{}},
120+
AzCopyTarget{dstObj, EExplicitCredentialType.PublicAuth(), CreateAzCopyTargetOptions{}},
121+
},
122+
Flags: CopyFlags{
123+
CopySyncCommonFlags: CopySyncCommonFlags{
124+
Recursive: pointerTo(true),
125+
},
126+
},
127+
ShouldFail: true,
128+
})
129+
130+
// Validate that the stdout contains these error URLs
131+
ValidateContainsError(svm, stdout, []string{"https://aka.ms/AzCopyError/NoAuthenticationInformation", "https://aka.ms/AzCopyError/ResourceNotFound"})
132+
}
133+
134+
func (s *BasicFunctionalitySuite) Scenario_Copy_EmptySASErrorCodes(svm *ScenarioVariationManager) {
135+
dstObj := CreateResource[ContainerResourceManager](svm, GetRootResource(svm, ResolveVariation(svm, []common.Location{common.ELocation.Local(), common.ELocation.Blob(), common.ELocation.File()})), ResourceDefinitionContainer{}).GetObject(svm, "test", common.EEntityType.File())
136+
137+
// Scale up from service to object
138+
srcObj := CreateResource[ObjectResourceManager](svm, GetRootResource(svm, ResolveVariation(svm, []common.Location{common.ELocation.Blob(), common.ELocation.File()})), ResourceDefinitionObject{})
139+
140+
if srcObj.Location().IsLocal() == dstObj.Location().IsLocal() {
141+
svm.InvalidateScenario()
142+
return
143+
}
144+
145+
stdout, _ := RunAzCopy(
146+
svm,
147+
AzCopyCommand{
148+
Verb: AzCopyVerbCopy,
149+
Targets: []ResourceManager{
150+
AzCopyTarget{srcObj, EExplicitCredentialType.PublicAuth(), CreateAzCopyTargetOptions{}},
151+
AzCopyTarget{dstObj, EExplicitCredentialType.PublicAuth(), CreateAzCopyTargetOptions{}},
152+
},
153+
Flags: CopyFlags{
154+
CopySyncCommonFlags: CopySyncCommonFlags{
155+
Recursive: pointerTo(true),
156+
},
157+
},
158+
ShouldFail: true,
159+
})
160+
161+
// Validate that the stdout contains these error URLs
162+
ValidateContainsError(svm, stdout, []string{"https://aka.ms/AzCopyError/NoAuthenticationInformation", "https://aka.ms/AzCopyError/ResourceNotFound"})
163+
}

e2etest/zt_newe2e_list_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,27 @@ func (s *ListSuite) Scenario_ListWithVersions(svm *ScenarioVariationManager) {
229229
expectedSummary := &cmd.AzCopyListSummary{FileCount: "4", TotalFileSize: "6.00 KiB"}
230230
ValidateListOutput(svm, stdout, expectedObjects, expectedSummary)
231231
}
232+
233+
func (s *ListSuite) Scenario_EmptySASErrorCodes(svm *ScenarioVariationManager) {
234+
// Scale up from service to object
235+
// TODO: update this test once File OAuth PR is merged bc current output is "azure files requires a SAS token for authentication"
236+
srcObj := CreateResource[ObjectResourceManager](svm, GetRootResource(svm, ResolveVariation(svm, []common.Location{common.ELocation.Blob(), common.ELocation.BlobFS()})), ResourceDefinitionObject{})
237+
238+
stdout, _ := RunAzCopy(
239+
svm,
240+
AzCopyCommand{
241+
Verb: AzCopyVerbList,
242+
Targets: []ResourceManager{
243+
AzCopyTarget{srcObj, EExplicitCredentialType.PublicAuth(), CreateAzCopyTargetOptions{}},
244+
},
245+
Flags: ListFlags{
246+
GlobalFlags: GlobalFlags{
247+
OutputType: to.Ptr(common.EOutputFormat.Json()),
248+
},
249+
},
250+
ShouldFail: true,
251+
})
252+
253+
// Validate that the stdout contains these error URLs
254+
ValidateErrorOutput(svm, stdout, "https://aka.ms/AzCopyError/NoAuthenticationInformation")
255+
}

e2etest/zt_newe2e_remove_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,23 @@ func (s *RemoveSuite) Scenario_SingleFileRemoveBlobFSEncodedPath(svm *ScenarioVa
5151
ObjectShouldExist: to.Ptr(false),
5252
}, false)
5353
}
54+
55+
func (s *RemoveSuite) Scenario_EmptySASErrorCodes(svm *ScenarioVariationManager) {
56+
// Scale up from service to object
57+
// File - ShareNotFound error
58+
// BlobFS - errors out in log file, not stdout
59+
srcObj := CreateResource[ObjectResourceManager](svm, GetRootResource(svm, ResolveVariation(svm, []common.Location{common.ELocation.Blob()})), ResourceDefinitionObject{})
60+
61+
stdout, _ := RunAzCopy(
62+
svm,
63+
AzCopyCommand{
64+
Verb: AzCopyVerbRemove,
65+
Targets: []ResourceManager{
66+
AzCopyTarget{srcObj, EExplicitCredentialType.PublicAuth(), CreateAzCopyTargetOptions{}},
67+
},
68+
ShouldFail: true,
69+
})
70+
71+
// Validate that the stdout contains these error URLs
72+
ValidateErrorOutput(svm, stdout, "https://aka.ms/AzCopyError/NoAuthenticationInformation")
73+
}

0 commit comments

Comments
 (0)